<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Buildmere Engineering | Blog</title><description/><link>http://localhost/</link><language>en</language><item><title>Are We Drifting? — Part 15: Structure Is the Rail</title><link>http://localhost/blog/are-we-drifting-15-structure-is-the-rail/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-15-structure-is-the-rail/</guid><description>A rule that has to find its targets by hand does not scale. A rule that targets a shape — a file name, a folder, a signature — scales to the whole fleet. So the deepest investment is not the rule. It is the structure the rule rides on.

</description><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-15-structure-is-the-rail&quot;&gt;Are We Drifting? — Part 15: Structure Is the Rail&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;Earlier parts treated structure and enforcement as two of the three legs of a &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Governed Sub-System (GSS)&quot; data-term-general=&quot;The unit of architectural governance: a structure (the canonical shape — a folder, file, type, or proto convention), a machine enforcement (a lint rule, compile check, or CI script that fails the build), and one documentation page. Without the enforcement leg, the structure is a wish.&quot; data-term-liftmere=&quot;9 frontend GSSs (Feature Layering, UI State System, Fixture Discipline, Design Tokens, Motion, etc.) and 9 backend GSSs (Contract Integrity, Domain Layering, Typed Error Model, etc.). Documented in the FE↔BE governance matrix.&quot; data-term-href=&quot;/reference/glossary#governed-sub-system&quot;&gt;governed sub-system&lt;/button&gt;. This part is about the relationship &lt;em&gt;between&lt;/em&gt; them — because it is a loop, and the loop is what makes the whole thing scale.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-loop&quot;&gt;The loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Structure gives enforcement the rails to scale.&lt;/p&gt;
&lt;p&gt;A rule that has to find its targets by hand does not scale: someone has to maintain the list, and the list drifts. A rule that targets a &lt;em&gt;shape&lt;/em&gt; — every file named &lt;code dir=&quot;auto&quot;&gt;screen.tsx&lt;/code&gt;, every package under &lt;code dir=&quot;auto&quot;&gt;internal/&lt;/code&gt;, every function returning &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; from a handler — scales to the whole fleet for free, because the shape is the selector. This is &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Opt-in by structure&quot; data-term-general=&quot;A guardrail that self-targets by a file, folder, type, or proto convention and stays dormant until you adopt the shape. Once you adopt the shape, enforcement activates for free — nothing is accidentally enforced on legacy code, and a new structural trigger can ship at error tier day one.&quot; data-term-liftmere=&quot;e.g. no-fixture-outside-route fires only in apps/client/src on non-route/story/test files. skeleton-composition fires only when a skeleton.tsx exists. no-uistate-screen-prop fires only on screen.tsx files.&quot; data-term-href=&quot;/reference/glossary#opt-in-by-structure&quot;&gt;opt-in by structure&lt;/button&gt;: adopt the shape and the enforcement attaches automatically; new code is governed the moment it takes the form, with no list to update.&lt;/p&gt;
&lt;p&gt;And enforcement is what keeps the structure intact.&lt;/p&gt;
&lt;p&gt;A folder convention with no gate is a folder convention until the first deadline, then it is a suggestion. The rule that forbids a fixture import outside a route is what keeps the layering &lt;em&gt;real&lt;/em&gt; — without it, “screens are pure” decays into “screens are mostly pure,” and the structure the next rule depends on is gone. Enforcement is how a structure stays a structure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Structure makes enforcement cheap to scale. Enforcement makes structure safe to rely on. Each one funds the other.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The loop compounds. More structure means cheaper, broader enforcement; broader enforcement protects more structure; protected structure is something the next rule can assume. The codebase ratchets toward more legible, not less.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;structure-is-not-one-level--it-is-every-level&quot;&gt;Structure is not one level — it is every level&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The word “structure” hides how many levels it runs at. The same loop — a convention paired with a gate — repeats from the coarsest grain to the finest. Each level’s gate is what lets the next level’s rule assume the level beneath it holds.&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Level&lt;/th&gt;&lt;th&gt;The structural convention&lt;/th&gt;&lt;th&gt;The gate that holds it&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Directory&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;a domain’s handler + repo co-located in &lt;code dir=&quot;auto&quot;&gt;internal/&amp;#x3C;domain&gt;/&lt;/code&gt;; a feature’s five layers in &lt;code dir=&quot;auto&quot;&gt;features/&amp;#x3C;family&gt;/&amp;#x3C;feature&gt;/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; — no cross-domain imports (lint-error)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;File name&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;screen.tsx&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;route.tsx&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;controller.ts&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;*.fixtures.ts&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;00001_*.sql&lt;/code&gt;&lt;/td&gt;&lt;td&gt;the rule self-targets &lt;em&gt;by the name&lt;/em&gt; — the name is the selector&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Module boundary&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;packages/ui&lt;/code&gt; is product-agnostic; the state system lives in one place&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;no-state-system-in-ui&lt;/code&gt;; middleware only in &lt;code dir=&quot;auto&quot;&gt;internal/server&lt;/code&gt; (depguard)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Imports&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;a screen never imports a fixture; a handler never imports &lt;code dir=&quot;auto&quot;&gt;pgx&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt;; &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; (handler ∌ pgx, repo ∌ connect/http)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Function signature&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;a metric is recorded through a generated, typed method; a handler returns a typed Connect error&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;RecordUploadIntentCreated(ctx, contentType, outcome)&lt;/code&gt; — labels are parameters; &lt;code dir=&quot;auto&quot;&gt;forbidigo&lt;/code&gt; bans bare &lt;code dir=&quot;auto&quot;&gt;fmt.Errorf&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Type shape&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;a handler implements the generated interface; a screen handles every state&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ ServiceHandler = (*Service)(nil)&lt;/code&gt; (compile); exhaustive &lt;code dir=&quot;auto&quot;&gt;matchState&lt;/code&gt; (compile)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read top to bottom and the grain gets finer — a directory, then a file, then a module edge, then a single import line, then the shape of one function, then a type. Read any row left to right and it is the same move every time: a shape, and a gate that refuses anything that is not the shape.&lt;/p&gt;
&lt;p&gt;The fine-grained levels are only affordable &lt;em&gt;because&lt;/em&gt; the coarse ones hold. &lt;code dir=&quot;auto&quot;&gt;no-fixture-outside-route&lt;/code&gt; can be a simple, file-granular rule precisely because the file-name convention (&lt;code dir=&quot;auto&quot;&gt;route.tsx&lt;/code&gt;) and the directory convention (one feature per folder) are already enforced beneath it. Pull out a lower level and the rules above it lose their footing. The structure is a stack, and each gate is a floor the next one stands on.&lt;/p&gt;
&lt;figure&gt; &lt;svg viewBox=&quot;0 0 720 358&quot; role=&quot;img&quot; aria-labelledby=&quot;ll-title ll-desc&quot;&gt; &lt;title id=&quot;ll-title&quot;&gt;Structure at every level&lt;/title&gt; &lt;desc id=&quot;ll-desc&quot;&gt;
Structure runs from directory down to type signature; each level pairs a
      convention with a gate, and each gate is a floor the next finer level
      stands on. The coarse levels are the foundation.
&lt;/desc&gt; &lt;!-- grain axis --&gt; &lt;line x1=&quot;40&quot; y1=&quot;34&quot; x2=&quot;40&quot; y2=&quot;328&quot; marker-end=&quot;url(#ll-up)&quot;&gt;&lt;/line&gt; &lt;defs&gt; &lt;marker id=&quot;ll-up&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;2&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto&quot;&gt; &lt;path d=&quot;M 1 9 L 5 1 L 9 9&quot;&gt;&lt;/path&gt; &lt;/marker&gt; &lt;/defs&gt; &lt;text x=&quot;22&quot; y=&quot;181&quot; transform=&quot;rotate(-90 22 181)&quot;&gt;finer grain →&lt;/text&gt; &lt;g&gt; &lt;rect x=&quot;86&quot; y=&quot;284&quot; width=&quot;604&quot; height=&quot;44&quot; rx=&quot;5&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;102&quot; y=&quot;311&quot;&gt;Directory&lt;/text&gt; &lt;text x=&quot;674&quot; y=&quot;311&quot;&gt;depguard · no cross-domain import&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;86&quot; y=&quot;234&quot; width=&quot;604&quot; height=&quot;44&quot; rx=&quot;5&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;102&quot; y=&quot;261&quot;&gt;File name&lt;/text&gt; &lt;text x=&quot;674&quot; y=&quot;261&quot;&gt;the name is the selector&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;86&quot; y=&quot;184&quot; width=&quot;604&quot; height=&quot;44&quot; rx=&quot;5&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;102&quot; y=&quot;211&quot;&gt;Module&lt;/text&gt; &lt;text x=&quot;674&quot; y=&quot;211&quot;&gt;no-state-system-in-ui&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;86&quot; y=&quot;134&quot; width=&quot;604&quot; height=&quot;44&quot; rx=&quot;5&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;102&quot; y=&quot;161&quot;&gt;Imports&lt;/text&gt; &lt;text x=&quot;674&quot; y=&quot;161&quot;&gt;no-fixture-outside-route&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;86&quot; y=&quot;84&quot; width=&quot;604&quot; height=&quot;44&quot; rx=&quot;5&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;102&quot; y=&quot;111&quot;&gt;Signature&lt;/text&gt; &lt;text x=&quot;674&quot; y=&quot;111&quot;&gt;generated typed params · forbidigo&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;86&quot; y=&quot;34&quot; width=&quot;604&quot; height=&quot;44&quot; rx=&quot;5&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;102&quot; y=&quot;61&quot;&gt;Type&lt;/text&gt; &lt;text x=&quot;674&quot; y=&quot;61&quot;&gt;var _ Iface = (*Service)(nil)&lt;/text&gt; &lt;/g&gt; &lt;text x=&quot;388&quot; y=&quot;350&quot;&gt;each gate is a floor the next level stands on&lt;/text&gt; &lt;/svg&gt; &lt;figcaption&gt;Structure at every level — the coarse conventions are the foundation; each finer rule stands on the gate beneath it. Conceptual, not measured data.&lt;/figcaption&gt; &lt;/figure&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-is-the-deepest-investment&quot;&gt;Why this is the deepest investment&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The instinct is to think of the lint rules as the asset. They are not. The rule is cheap — especially now. The asset is the &lt;strong&gt;structure the rule rides on&lt;/strong&gt;, because that is what makes the rule general instead of bespoke, and what makes the &lt;em&gt;next&lt;/em&gt; rule possible at all.&lt;/p&gt;
&lt;p&gt;This is why “add a guardrail” is rarely the first move. The first move is to give the thing a shape — a canonical file, a folder, a naming convention, a typed signature — and the guardrail follows almost for free, because it has something to target. A team that invests in structure gets enforcement at a discount forever after. A team that writes rules against an unstructured codebase pays full price for every one and watches them rot.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The coherence check this removes is the one that does not look like a check at all: &lt;em&gt;where does this go, and what shape does it take?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When structure is enforced at every level, that question has one answer per level, and the answer is mechanically true — a new domain is a folder, a new feature is five named files, a new metric is a typed method, a new handler implements a generated interface. An agent does not guess the shape; the shape is the convention, and the gate refuses the guess that misses it. You accept a large generated change and trust it not because you reviewed the placement of every file, but because misplacement is a shape the build does not allow.&lt;/p&gt;
&lt;p&gt;That is the rail metaphor made literal. Rails do not make a train fast. They make it possible to go fast &lt;em&gt;safely&lt;/em&gt; — and to add another train without re-surveying the route.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Structure is the rail; the gates are placed along it. The rest of the series walks specific rails — the vocabularies, the operations, the agents — and a &lt;a href=&quot;http://localhost/blog/are-we-drifting-14-anti-drift-and-the-honest-edge&quot; title=&quot;The Wall of Gates and What Comes Next&quot;&gt;running inventory of the gates&lt;/a&gt;. The question stays the same at every level, coarse or fine.&lt;/p&gt;
&lt;p&gt;Are we drifting? Not where the shape is named and a gate holds it — and the more levels that is true at, the less there is left to drift.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 14: The Wall of Gates and What Comes Next</title><link>http://localhost/blog/are-we-drifting-14-anti-drift-and-the-honest-edge/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-14-anti-drift-and-the-honest-edge/</guid><description>One shape governed every layer: one source, many projections, drift gates. Here is the whole wall of gates in one place, an honest map of what is not done, and the answer to the question the series kept asking.

</description><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-14-the-wall-of-gates-and-what-comes-next&quot;&gt;Are We Drifting? — Part 14: The Wall of Gates and What Comes Next&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;Every layer of the stack turns out to be the same move: &lt;strong&gt;one declarative source → many generated &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Projection&quot; data-term-general=&quot;An artifact generated from a single source — a view of the truth, not the truth itself. A projection can always be regenerated from its source; if it cannot, it is a copy, not a projection. Copies drift; projections are kept honest by a drift gate.&quot; data-term-liftmere=&quot;Generated .gen.go files (buildmere), features.generated.ts (generate-features), Postgres read-model tables (event log replay). Each has a paired drift gate.&quot; data-term-href=&quot;/reference/glossary#projection&quot;&gt;projections&lt;/button&gt; → &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Drift gate&quot; data-term-general=&quot;A check that runs in CI and fails the build when a committed projection diverges from what its source would generate. The load-bearing leg of the governance model — without it, &amp;#x22;generated&amp;#x22; is just a suggestion.&quot; data-term-liftmere=&quot;make check-vocab (buildmere), generate-features --check (feature registry), make check-sqlc, make check-mocks, make agents-check. All exit non-zero on any byte of drift.&quot; data-term-href=&quot;/reference/glossary#drift-gate&quot;&gt;drift gates&lt;/button&gt; keep them honest.&lt;/strong&gt; This part collects the gates built so far, grades them on the ladder, answers the title, and points at where the pattern goes next.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-wall-in-one-place&quot;&gt;The wall, in one place&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A guardrail that does not run is theatre. So here is the actual set of checks that run — most on every pull request — each tagged with its tier and the part of the series that covers it:&lt;/p&gt;



































































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Gate&lt;/th&gt;&lt;th&gt;What it catches&lt;/th&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;th&gt;Series&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ ServiceHandler = (*Service)(nil)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;handler drifts from generated interface&lt;/td&gt;&lt;td&gt;1 — compile&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;tsc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;exhaustive &lt;code dir=&quot;auto&quot;&gt;matchState&lt;/code&gt;, typed everything&lt;/td&gt;&lt;td&gt;1 — compile&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt;&lt;/td&gt;&lt;td&gt;fixture imported outside route/story/test&lt;/td&gt;&lt;td&gt;2 — lint-error&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-12-testing-without-drift&quot;&gt;Part 12: Testing Without Drift&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-raw-hex-color&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;no-raw-tok-vars&lt;/code&gt;&lt;/td&gt;&lt;td&gt;raw color values in component code&lt;/td&gt;&lt;td&gt;2 — lint-error (ambient)&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-uistate-screen-prop&lt;/code&gt;&lt;/td&gt;&lt;td&gt;parallel &lt;code dir=&quot;auto&quot;&gt;uiState&lt;/code&gt; prop on a screen&lt;/td&gt;&lt;td&gt;2 — lint-error&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-null-suspense-fallback&lt;/code&gt;&lt;/td&gt;&lt;td&gt;blank first paint in the router&lt;/td&gt;&lt;td&gt;2 — lint-error&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-smart-quotes&lt;/code&gt;&lt;/td&gt;&lt;td&gt;curly quotes in TS string literals&lt;/td&gt;&lt;td&gt;2 — lint-error (ambient)&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-direct-event-log-append&lt;/code&gt;&lt;/td&gt;&lt;td&gt;raw event-log writes outside the sanctioned writer&lt;/td&gt;&lt;td&gt;2 — lint-error&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-8-projected-truth&quot;&gt;Part 8: Projected Truth&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;buf lint&lt;/code&gt;&lt;/td&gt;&lt;td&gt;proto contract stays well-formed&lt;/td&gt;&lt;td&gt;2 — lint-error&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-vocab&lt;/code&gt;&lt;/td&gt;&lt;td&gt;committed &lt;code dir=&quot;auto&quot;&gt;.gen.go&lt;/code&gt; drifts from its YAML manifest&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-3-buildmere&quot; title=&quot;buildmere, a Codegen Kernel&quot;&gt;Parts 3–6&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-sqlc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;generated DB models drift from hand-authored SQL&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-7-models-at-boundaries&quot;&gt;Part 7: Models at Boundaries&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-mocks&lt;/code&gt;&lt;/td&gt;&lt;td&gt;generated mocks drift from their interfaces&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-12-testing-without-drift&quot;&gt;Part 12: Testing Without Drift&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-connect-errors&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;connect.NewError&lt;/code&gt; called outside the error adapter&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-5-the-error-manifest&quot;&gt;Part 5: The Error Manifest&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-sql-scope&lt;/code&gt;&lt;/td&gt;&lt;td&gt;owner-scoped table queried without a &lt;code dir=&quot;auto&quot;&gt;user_id&lt;/code&gt; filter&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-7-models-at-boundaries&quot;&gt;Part 7: Models at Boundaries&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-migrations.sh&lt;/code&gt;&lt;/td&gt;&lt;td&gt;migration file edited after applied&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-8-projected-truth&quot;&gt;Part 8: Projected Truth&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;generate-features --check&lt;/code&gt;&lt;/td&gt;&lt;td&gt;committed feature registry is stale&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;defineMcpServerContract&lt;/code&gt;&lt;/td&gt;&lt;td&gt;MCP tool surface diverges from the operations registry&lt;/td&gt;&lt;td&gt;3 — contract test&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-9-one-operation-three-interfaces&quot;&gt;Part 9: One Operation, Three Interfaces&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make agents-check&lt;/code&gt;&lt;/td&gt;&lt;td&gt;rendered agent file diverges from its manifest&lt;/td&gt;&lt;td&gt;3 — CI script&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-10-the-agent-os&quot;&gt;Part 10: The Agent OS&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;rebuild-from-log&lt;/code&gt;&lt;/td&gt;&lt;td&gt;any projection is reproducible from the event log&lt;/td&gt;&lt;td&gt;recoverable&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-8-projected-truth&quot;&gt;Part 8: Projected Truth&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt; (burndown)&lt;/td&gt;&lt;td&gt;~113 remaining violations&lt;/td&gt;&lt;td&gt;4 — warn (scoreboard)&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-12-testing-without-drift&quot;&gt;Part 12: Testing Without Drift&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;No single one of these is clever. Together they are the difference between “we have a convention” and “the convention is true.” Each removes a coherence check a human would otherwise perform by eye — and each is the reason a large, AI-generated diff can be &lt;em&gt;trusted&lt;/em&gt; instead of re-audited.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-ladder-keeps-it-honest&quot;&gt;The ladder keeps it honest&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Not every rule sits at the same strength, and pretending otherwise would itself be drift — between what a doc claims and what the build enforces. So every guardrail carries a tier on a shared &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Maturity ladder&quot; data-term-general=&quot;A five-tier scale describing how strongly a governance rule is enforced: (1) compile-enforced — cannot ship; (2) lint-error — PR fails CI; (3) coverage-script — CI script fails on a missing pairing; (4) warn / scoreboard — tracked, non-blocking, during a burndown; (5) convention — documented but unenforced. The tier makes a rule&amp;#x27;s real status legible at a glance.&quot; data-term-liftmere=&quot;Shared across FE and BE. Frontend mostly tiers 1–2 (13 custom ESLint rules + tsc). Backend has a compile-gated core with several sub-systems still climbing from tier 5 toward wired linters.&quot; data-term-href=&quot;/reference/glossary#maturity-ladder&quot;&gt;maturity ladder&lt;/button&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;1  compile-enforced   the compiler/type system catches it — cannot ship&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;2  lint-error         a rule at error — PR fails CI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;3  coverage-script    a CI script fails on a missing pairing or forbidden diff&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;4  warn (scoreboard)  reported, non-blocking, during a burndown to zero&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;5  convention         documented but unenforced — a known gap, a future target&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The tier tells you the &lt;em&gt;real&lt;/em&gt; status of a rule at a glance. A sub-system at tier 5 is honest about being a wish; one at tier 1 cannot be violated. The frontend mostly lives at tiers 1–2; the backend has a genuinely-gated core and a set of sub-systems still climbing from convention toward a wired linter. The point of the ladder is not to claim everything is enforced — it is to make the &lt;em&gt;gaps legible&lt;/em&gt; so they can be closed on purpose.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-the-gate-fires&quot;&gt;When the gate fires&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Strength is one axis; &lt;em&gt;timing&lt;/em&gt; is the other. A gate runs at some point in the life of a change — and the earlier it fires, the cheaper the failure.&lt;/p&gt;
&lt;figure&gt; &lt;svg viewBox=&quot;0 0 720 300&quot; role=&quot;img&quot; aria-labelledby=&quot;tl-title tl-desc&quot;&gt; &lt;title id=&quot;tl-title&quot;&gt;When the gate fires&lt;/title&gt; &lt;desc id=&quot;tl-desc&quot;&gt;
A gate can fire at commit-time, at CI, at compile, or at runtime; the cost
      of the failure it catches rises the later it fires, so commit-time is the
      cheapest place to catch drift.
&lt;/desc&gt; &lt;!-- axes --&gt; &lt;line x1=&quot;64&quot; y1=&quot;226&quot; x2=&quot;690&quot; y2=&quot;226&quot;&gt;&lt;/line&gt; &lt;line x1=&quot;64&quot; y1=&quot;226&quot; x2=&quot;64&quot; y2=&quot;44&quot;&gt;&lt;/line&gt; &lt;text x=&quot;22&quot; y=&quot;135&quot; transform=&quot;rotate(-90 22 135)&quot;&gt;cost of the failure →&lt;/text&gt; &lt;g&gt; &lt;rect x=&quot;98.25&quot; y=&quot;184&quot; width=&quot;88&quot; height=&quot;42&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;142.25&quot; y=&quot;176&quot;&gt;pre-commit hooks&lt;/text&gt; &lt;text x=&quot;142.25&quot; y=&quot;248&quot;&gt;commit-time&lt;/text&gt; &lt;text x=&quot;142.25&quot; y=&quot;265&quot;&gt;seconds · local&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;254.75&quot; y=&quot;142&quot; width=&quot;88&quot; height=&quot;84&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;298.75&quot; y=&quot;134&quot;&gt;the lint umbrella&lt;/text&gt; &lt;text x=&quot;298.75&quot; y=&quot;248&quot;&gt;CI / PR&lt;/text&gt; &lt;text x=&quot;298.75&quot; y=&quot;265&quot;&gt;minutes · remote&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;411.25&quot; y=&quot;100&quot; width=&quot;88&quot; height=&quot;126&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;455.25&quot; y=&quot;92&quot;&gt;the type system&lt;/text&gt; &lt;text x=&quot;455.25&quot; y=&quot;248&quot;&gt;compile&lt;/text&gt; &lt;text x=&quot;455.25&quot; y=&quot;265&quot;&gt;in the build&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;567.75&quot; y=&quot;58&quot; width=&quot;88&quot; height=&quot;168&quot; rx=&quot;4&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;611.75&quot; y=&quot;50&quot;&gt;a request, a boot&lt;/text&gt; &lt;text x=&quot;611.75&quot; y=&quot;248&quot;&gt;runtime&lt;/text&gt; &lt;text x=&quot;611.75&quot; y=&quot;265&quot;&gt;production&lt;/text&gt; &lt;/g&gt; &lt;!-- direction + endpoints --&gt; &lt;text x=&quot;142.25&quot; y=&quot;286&quot;&gt;cheapest&lt;/text&gt; &lt;text x=&quot;611.75&quot; y=&quot;286&quot;&gt;an incident&lt;/text&gt; &lt;text x=&quot;377&quot; y=&quot;294&quot;&gt;earlier ───────────────▶ later&lt;/text&gt; &lt;/svg&gt; &lt;figcaption&gt;The timing axis of the wall — conceptual, not measured. The earlier a gate fires, the cheaper the failure it catches.&lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;The same gate is a different cost at each tier. A migration mistake refused on &lt;code dir=&quot;auto&quot;&gt;git commit&lt;/code&gt; costs the developer ten seconds; the same mistake caught at runtime is an incident. So the wall is not only a set of rules — it is a set of rules placed &lt;em&gt;as early as each one can run.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The earliest tier is the pre-commit subsystem: a handful of hooks that fire on every commit, before code reaches CI — a secret scan, &lt;code dir=&quot;auto&quot;&gt;buf lint&lt;/code&gt;, migration discipline, the &lt;code dir=&quot;auto&quot;&gt;connect.NewError&lt;/code&gt; boundary check, the owner-scope check. Several of them appear in the wall above &lt;em&gt;and&lt;/em&gt; run again in CI on purpose: the commit-time copy exists for the feedback loop, the CI copy for the guarantee, so bypassing local hooks still gets caught at the PR. The full commit-time tier — every hook and why it earns its place at the front — is its own reference: &lt;a href=&quot;http://localhost/reference/commit-time-gates/&quot;&gt;Commit-time gates&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A gate’s value is inseparable from when it fires. Move the check as early as it can run, and the expensive version of the failure never happens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;so--are-we-drifting&quot;&gt;So — are we drifting?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;On every layer where a gate runs: &lt;strong&gt;no, structurally.&lt;/strong&gt; A value cannot exist on one side that another side has not heard of. A generated file cannot disagree with its manifest. A handler cannot diverge from its contract. An agent cannot behave differently across frameworks. A projection cannot win an argument with the event log. Those are not promises maintained by discipline; they are properties maintained by the build, on every PR, by machines rather than memory.&lt;/p&gt;
&lt;p&gt;On the layers where a gate does not yet run: &lt;strong&gt;maybe — and we know exactly where.&lt;/strong&gt; That is what the ladder has been tracking the whole way, and what the next section builds on.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-this-goes-next&quot;&gt;Where this goes next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the frontier the whole pattern points at — the next set of seams that want to become one source with many projections and a gate.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The capstone: a frontend form and its backend validation from one manifest.&lt;/strong&gt; This is the unbuilt piece the pattern keeps pointing at — the one place where the shape exists in every adjacent layer but has not yet been closed. The plumbing is already there — operation inputs are Zod schemas, &lt;code dir=&quot;auto&quot;&gt;zod-to-json-schema&lt;/code&gt; is in use, the vocabularies generate validators — and the natural extension is to generate a &lt;em&gt;form&lt;/em&gt; and its &lt;em&gt;server-side validation&lt;/em&gt; from a single declaration. When that lands, the tightest FE↔BE seam there is — “what may the user submit, and what will the server accept?” — becomes one source with two projections and a gate. That is where this goes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More vocabulary kinds.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;permission&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;route&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;event&lt;/code&gt; are sketched in buildmere’s plugin contract, and bringing them under the same manifest is the next reach; permissions and routes are governed by other means today.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More language emitters.&lt;/strong&gt; The &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; kind emits Go; a TypeScript emitter generates the frontend’s error constants and closes the &lt;a href=&quot;http://localhost/blog/are-we-drifting-5-the-error-manifest&quot;&gt;Part 5: The Error Manifest&lt;/a&gt; coupling completely. The same move applies to a metric TypeScript emitter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;buf breaking&lt;/code&gt; as a blocking gate.&lt;/strong&gt; The rule is configured; wiring it to fail CI while the proto is mid-rename is the next turn of the ratchet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production observability.&lt;/strong&gt; Config declares an OTLP endpoint, and the direction is full production OTLP with real spans built on top of the first tracing cut.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A devportal-state CLI fallback&lt;/strong&gt; and a pair of platform adapters (&lt;code dir=&quot;auto&quot;&gt;AGENTS.md&lt;/code&gt;, a Cursor &lt;code dir=&quot;auto&quot;&gt;_platform.mdc&lt;/code&gt;) extend the agent-OS pattern to the next surfaces.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-works-now&quot;&gt;Why this works now&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Step back to where &lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt; started. Two forces make this the right architecture for &lt;em&gt;this&lt;/em&gt; moment, not just good hygiene.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The rails unlock velocity.&lt;/strong&gt; When writing code and recalling patterns are cheap, the scarce resource is coherence. The gates above are what let you accept a large generated diff and trust it, because every way it could drift is a way the build refuses. The speed is not the typing — it is the not-having-to-recheck.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And the rails got cheap to build.&lt;/strong&gt; The old reason this kind of governance was rare is that a manifest system, a custom linter, a codegen kernel, an event-projection plane were expensive to build and maintain. The same economics that made generation cheap collapsed that cost: a new vocabulary kind, a new lint rule, a new gate is now an afternoon. The force that created the drift problem is the one that funds the cure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Constraints are not the brake on cheap generation. They are what makes it safe to trust — and trust is what lets you go fast. “Governance is expensive” is an old-world assumption that no longer holds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;the-question-is-permanent&quot;&gt;The question is permanent&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;“Are we drifting?” is not a question you answer once. It is asked continuously — by &lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; on every commit, by the contract test on every MCP boot, by &lt;code dir=&quot;auto&quot;&gt;agents-check&lt;/code&gt; on every sync, by the compiler on every build. The work was never to answer it by hand. The work was to build the gates that ask it for you, forever, and to be honest about the few places they do not run yet.&lt;/p&gt;
&lt;p&gt;Define the concept once. Generate the rest. Let the gates keep them honest. That is the pattern — and as the system grows, each new layer joins the same shape. This corpus grows with it: the question does not change, the gates keep answering it, and new parts land as new seams come under the same discipline.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 13: AI Output Is Untrusted Input</title><link>http://localhost/blog/are-we-drifting-13-ai-structured-output/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-13-ai-structured-output/</guid><description>A model will happily hand you a status that does not exist. So you treat its output the way you treat any untrusted boundary — declare the shape, validate against it, retry on failure — and the enum manifests you already have become the exact set it is forced to choose from.

</description><pubDate>Sun, 14 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-13-ai-output-is-untrusted-input&quot;&gt;Are We Drifting? — Part 13: AI Output Is Untrusted Input&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;This series has been about keeping representations of a concept from drifting apart. A language model is the most enthusiastic drift generator you can plug in: ask it for a status and it may invent one; ask it for a number and it may send a string; ask it for JSON and it may return &lt;em&gt;almost&lt;/em&gt; JSON. This part is about putting it behind the same kind of gate as everything else.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The instinct is to treat a model’s output as data, because it looks like data. It is not. It is a &lt;em&gt;suggestion&lt;/em&gt; shaped like data, produced by a process with no obligation to your types.&lt;/p&gt;
&lt;p&gt;So it drifts in every direction at once. It returns &lt;code dir=&quot;auto&quot;&gt;&quot;Done&quot;&lt;/code&gt; when your enum is &lt;code dir=&quot;auto&quot;&gt;ready&lt;/code&gt;. It collapses a nested object into a string. It hallucinates a field. It emits a difficulty of &lt;code dir=&quot;auto&quot;&gt;&quot;very hard&quot;&lt;/code&gt; when the allowed set is &lt;code dir=&quot;auto&quot;&gt;beginner | intermediate | advanced&lt;/code&gt;. None of this is a bug in the model — it is the model doing exactly what it does. The bug is letting that output into your system unchecked, where it becomes a row, an event, a decision — a piece of drift with a long half-life.&lt;/p&gt;
&lt;p&gt;The question is the one you would ask of any external system: is this untrusted shape forced to conform to a contract before it crosses the boundary, or is it trusted on sight?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-principle-a-model-is-a-boundary-like-any-other&quot;&gt;The principle: a model is a boundary, like any other&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You already have a discipline for untrusted input. A web form is validated before it becomes a command. A third-party API’s response is mapped to your own model at the adapter and never leaked past it. A model is the same kind of boundary, and gets the same treatment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Declare the expected output shape&lt;/strong&gt; — as a schema, not a hope.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Communicate the shape to the model&lt;/strong&gt; — via structured-output / JSON-Schema constraints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validate the response&lt;/strong&gt; against the shape.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On failure, retry with the validation error as feedback&lt;/strong&gt; — bounded to a couple of attempts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Surface a clean typed value, or a normal application error&lt;/strong&gt; — never a raw, half-valid blob.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The frontend has done this for years: a Zod schema validates an untrusted shape at the edge before it is allowed in. The same idea in Go is a schema-tagged struct plus JSON-Schema generation plus validation plus a retry loop. Different language, identical rule:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Untrusted shapes must conform to a declared contract before they cross the boundary. A model is untrusted. Validate it like a form, isolate it like a vendor.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here is how the boundary works. The raw provider types — the OpenAI or Anthropic response objects — stop at the adapter. The application talks to a typed interface (&lt;code dir=&quot;auto&quot;&gt;Summarize(ctx, input) (domain.WorkoutSummary, error)&lt;/code&gt;) and receives a validated domain value or a clean error. A schema mismatch is a breaking change, not a runtime surprise.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-vocabularies-become-the-models-contract&quot;&gt;The vocabularies become the model’s contract&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is where the series closes a loop.&lt;/p&gt;
&lt;p&gt;The single most effective way to stop a model from inventing a value is to &lt;em&gt;give it the closed set to choose from.&lt;/em&gt; And the closed set already exists — it is the enum vocabulary from &lt;a href=&quot;http://localhost/blog/are-we-drifting-4-enums&quot;&gt;Part 4: Enums as Shared Vocabulary&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The same manifest that generated the Go constants, the TypeScript union, and the SQL &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint can generate the &lt;strong&gt;enum constraint in the JSON Schema sent to the model.&lt;/strong&gt; So the model is handed exactly the set of &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; values the rest of the system recognizes, and structured-output enforcement makes a value outside that set impossible to return. Then validation on the way back checks membership against the &lt;em&gt;same&lt;/em&gt; generated &lt;code dir=&quot;auto&quot;&gt;IsValid()&lt;/code&gt; — and because the model was constrained by the manifest and validated against the manifest, a value it returns is, by construction, a value every other layer already agrees about.&lt;/p&gt;
&lt;p&gt;The vocabulary that prevented drift between your database and your frontend now prevents drift between your &lt;em&gt;model&lt;/em&gt; and your database. One source; one more projection; the boundary gated the same way.&lt;/p&gt;
&lt;p&gt;The concrete version: &lt;code dir=&quot;auto&quot;&gt;VideoStatus&lt;/code&gt; is declared once in the manifest with five wire values — &lt;code dir=&quot;auto&quot;&gt;uploading&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;processing&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ready&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;failed&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;archived&lt;/code&gt; — and every layer gets that same closed set derived from it.&lt;/p&gt;
&lt;p&gt;The JSON Schema fragment that goes to the model is generated from the same manifest:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;VideoStatus&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;enum&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The model cannot return &lt;code dir=&quot;auto&quot;&gt;&quot;done&quot;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;&quot;in_progress&quot;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;&quot;complete&quot;&lt;/code&gt; — the structured-output constraint refuses it before the response even arrives. Then &lt;code dir=&quot;auto&quot;&gt;IsValid()&lt;/code&gt; on the way back in checks membership against the same five values, so what reaches the domain layer is a &lt;code dir=&quot;auto&quot;&gt;VideoStatus&lt;/code&gt; that every other layer — Go, TypeScript, SQL — was generated to agree with.&lt;/p&gt;
&lt;p&gt;And when the model does fail validation, that failure is itself a declared thing — an entry in the error vocabulary from &lt;a href=&quot;http://localhost/blog/are-we-drifting-5-the-error-manifest&quot;&gt;Part 5: The Error Manifest&lt;/a&gt; (&lt;code dir=&quot;auto&quot;&gt;AI_STRUCTURED_OUTPUT_INVALID&lt;/code&gt;, retryable), normalized at the adapter exactly like a billing provider’s failure. The unhappy path of the AI boundary is governed by the same manifest as every other failure.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The check this removes is the one teams skip until it burns them: &lt;em&gt;did the model actually return the shape I assumed, with values my system recognizes?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When the model is constrained by a generated schema and validated against the same vocabulary, you can let it produce structured data and &lt;em&gt;trust the result&lt;/em&gt; — not because the model is reliable, but because the boundary is. Invalid output is rejected and retried; what reaches your domain is a typed value drawn from sets the rest of the stack already shares. The model becomes a normal, gated input source, which means you can build on it at the speed you build on a form — instead of treating every AI feature as a fragile special case to be hand-audited.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-14-anti-drift-and-the-honest-edge&quot;&gt;Part 14: The Wall of Gates and What Comes Next&lt;/a&gt; collects the full wall of drift gates the series has walked past, asks the title question one last time, and is specific about where the work goes next.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 12: Testing Without Drift</title><link>http://localhost/blog/are-we-drifting-12-testing-without-drift/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-12-testing-without-drift/</guid><description>A green test is only worth something if it still resembles production. Fixtures shared across every surface, real databases for the data layer, and injected time keep the test world from quietly drifting away from the real one.

</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-12-testing-without-drift&quot;&gt;Are We Drifting? — Part 12: Testing Without Drift&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;A test suite is supposed to be the thing that &lt;em&gt;catches&lt;/em&gt; drift. But tests are code too, and they have their own way of drifting — away from the production they claim to verify. This part is about keeping the test world honest.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The most dangerous test is the one that is green and wrong.&lt;/p&gt;
&lt;p&gt;It happens whenever a test asserts against a &lt;em&gt;copy&lt;/em&gt; of reality that has drifted. A repository test runs against a mocked database and passes — while the real migration it depends on is broken, so production fails on the exact path the test “covered.” A screen test renders hand-built sample data that no longer looks like any real product state, so it passes while the actual empty-state crashes. A test depends on &lt;code dir=&quot;auto&quot;&gt;time.Now()&lt;/code&gt; and is flaky at midnight.&lt;/p&gt;
&lt;p&gt;In each case the suite is green and the system is broken, because the test drifted from the thing it was meant to mirror. So the question for testing is: do the tests still resemble production — or have they drifted into a comfortable fiction?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;fixtures-are-a-vocabulary-of-product-states&quot;&gt;Fixtures are a vocabulary of product states&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The frontend’s answer starts from the same primitive as everything else in this series: a closed, canonical set, declared once, shared everywhere.&lt;/p&gt;
&lt;p&gt;Fixtures are not throwaway sample data. They are the &lt;strong&gt;canonical vocabulary of product states&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;regular&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;empty&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;loaded&lt;/code&gt;, and the &lt;code dir=&quot;auto&quot;&gt;loading&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; arms when a screen has them — defined once per feature with &lt;code dir=&quot;auto&quot;&gt;defineFixtures&lt;/code&gt;, and shared by Storybook, the screen test, the route’s initial state, QA, and AI prototyping. One set of states, consumed by every surface.&lt;/p&gt;
&lt;p&gt;That sharing is the anti-drift mechanism. Because the Storybook story, the test, and the running route all seed from the &lt;em&gt;same&lt;/em&gt; fixtures, a story cannot depict a state the test never checks, and neither can drift from the shape the route actually renders. And a governed rule (FE-1) keeps the vocabulary from leaking: screens may never import fixtures — only routes, stories, and tests may — enforced by a lint rule, so the seam is structural. It is the same move as the backend’s table-driven tests, where each case names a domain state (valid input, missing field, not-found, conflict) and the set of named cases &lt;em&gt;is&lt;/em&gt; the spec.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fixtures are product states, not test data. When the same named states feed the workbench, the tests, QA, and the live route, the test world cannot drift from the product world.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;real-infrastructure-for-the-data-layer&quot;&gt;Real infrastructure for the data layer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The backend’s hardest-won rule is blunt: &lt;strong&gt;the repository is tested against a real Postgres — a container — never a mock.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The reason is a scar. Mocked repository tests once passed green while the migration they relied on failed in production. A mock of a database is a copy of your &lt;em&gt;beliefs&lt;/em&gt; about the database, and beliefs drift from schemas. So adapter-level tests run against real Postgres (and real MinIO, real queues) in an isolated, throwaway container, behind a build tag so they are opt-in locally and required in CI.&lt;/p&gt;
&lt;p&gt;There is a clean division of labor that keeps this from becoming “test everything against everything”:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Fakes verify application behavior.        (fast, in-memory, deterministic)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Real containers verify adapter behavior.  (does the SQL actually run? does the constraint fire?)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Each level verifies what only it can.     (do not duplicate coverage)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A fake repository is the right tool to test a service’s &lt;em&gt;logic&lt;/em&gt; — it is fast and controllable. A real Postgres is the only tool that can tell you the &lt;em&gt;query&lt;/em&gt; and the &lt;em&gt;migration&lt;/em&gt; and the &lt;em&gt;constraint&lt;/em&gt; actually work, because those are the parts a mock can only pretend to have. Test the logic against fakes; test the adapter against the real thing; do not make either re-verify the other.&lt;/p&gt;
&lt;p&gt;So across both stacks, the answer to “are we drifting here?” is not “probably not” — it is a set of named gates that each refuse a specific known failure mode.&lt;/p&gt;
&lt;p&gt;On the frontend, the first gate is &lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;tools/eslint-no-fixture-outside-route.mjs&lt;/code&gt;), which exists because screens pulling fixtures in directly breaks the shared-vocabulary seam — it is a lint error, not a convention. The second is &lt;code dir=&quot;auto&quot;&gt;check-fixture-registry-coverage.mjs&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;apps/client/scripts/&lt;/code&gt;), which catches collections that were defined but never registered, making them invisible to App Mode’s preset injection and therefore untestable by anyone who doesn’t know to look.&lt;/p&gt;
&lt;p&gt;The third gate has a specific story behind it.&lt;/p&gt;
&lt;p&gt;A plate-calculation bug once passed every unit test and failed in the live dev app because it involved a &lt;code dir=&quot;auto&quot;&gt;setState&lt;/code&gt;-during-render that only surfaces when React runs in &lt;code dir=&quot;auto&quot;&gt;StrictMode&lt;/code&gt; — which double-invokes effects in development to expose exactly this class of mistake. The unit tests did not see it because they bypassed the &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;StrictMode&gt;&lt;/code&gt; wrapper by importing directly from &lt;code dir=&quot;auto&quot;&gt;@testing-library/react&lt;/code&gt;. So we added a &lt;code dir=&quot;auto&quot;&gt;no-restricted-imports&lt;/code&gt; rule on test files: import from &lt;code dir=&quot;auto&quot;&gt;~test-utils&lt;/code&gt; instead, which wraps the renderer in &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;StrictMode&gt;&lt;/code&gt; automatically. The bug can no longer pass a test that would have missed it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A test that does not run in StrictMode is not testing what the development app runs. The gate makes the test environment and the live environment agree.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On the backend, the integration-test gate exists for the same reason as the real-Postgres rule: a mocked repository cannot lie to you in a way a real migration will catch. Adapter-level tests run under a &lt;code dir=&quot;auto&quot;&gt;//go:build integration&lt;/code&gt; tag and are invoked via &lt;code dir=&quot;auto&quot;&gt;make test-be-integration&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;go test -tags=integration -race ./...&lt;/code&gt;), which also enables the race detector — so the gate is both a correctness check on the SQL and a concurrency check on the adapter code.&lt;/p&gt;
&lt;p&gt;The final gate is &lt;code dir=&quot;auto&quot;&gt;check-mocks&lt;/code&gt;, which runs in CI to verify that &lt;code dir=&quot;auto&quot;&gt;mockgen&lt;/code&gt;-produced fakes still match the interfaces they were generated from. When an interface changes, the mock changes too, or the build fails. An interface and its mock cannot silently diverge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The five gates, in one place&lt;/strong&gt;&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift mode&lt;/th&gt;&lt;th&gt;Named gate&lt;/th&gt;&lt;th&gt;What it catches&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Fixture imported by a screen&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;tools/eslint-no-fixture-outside-route.mjs&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Screens, flows, runtimes pulling fixtures in directly, breaking the shared-vocabulary seam&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A &lt;code dir=&quot;auto&quot;&gt;defineFixtures&lt;/code&gt; collection not in the registry&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-fixture-registry-coverage.mjs&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;apps/client/scripts/&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Collections that exist but are invisible to App Mode preset injection&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Effect-lifecycle bug that only shows in dev&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;no-restricted-imports&lt;/code&gt; of &lt;code dir=&quot;auto&quot;&gt;@testing-library/react&lt;/code&gt; — import from &lt;code dir=&quot;auto&quot;&gt;~test-utils&lt;/code&gt; instead&lt;/td&gt;&lt;td&gt;Bypassing the &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;StrictMode&gt;&lt;/code&gt; wrapper that double-invokes effects; the motivating case was a plate-calc &lt;code dir=&quot;auto&quot;&gt;setState&lt;/code&gt;-during-render regression that passed unit tests but failed in the live dev app&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Mocked repo that lies about the schema&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;//go:build integration&lt;/code&gt; + &lt;code dir=&quot;auto&quot;&gt;make test-be-integration&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;go test -tags=integration -race ./...&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Repo tests that pass against a mock while the real migration is broken — the scar this rule came from&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Generated mock out of sync with its interface&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-mocks&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;mockgen&lt;/code&gt;-produced fakes that no longer match the interface they were generated from&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Each gate fires in CI. None of them require a human to remember the rule.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;determinism-dont-let-reality-leak-in&quot;&gt;Determinism: don’t let reality leak in&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The last source of test drift is nondeterminism. A test that depends on the wall clock, a random ID, or ambient ordering passes and fails for reasons that have nothing to do with the code.&lt;/p&gt;
&lt;p&gt;The discipline is to &lt;strong&gt;inject&lt;/strong&gt; the nondeterministic things — time, ID generation, randomness — behind tiny interfaces, so a test supplies a fixed clock and a deterministic ID sequence. The same application, wired with a fake clock and seeded IDs, produces the same output every run. Combined with fakes for infrastructure, this is what lets a system-level harness exercise the &lt;em&gt;real&lt;/em&gt; wiring of the app — real services, real flow — against fake, deterministic edges. The test runs the production code path; only the world at the edges is controlled.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The check this removes is the one you only discover you skipped when production breaks: &lt;em&gt;does this test actually correspond to the real system, or to a copy that has wandered?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When fixtures are shared product states, a passing screen test means the workbench, QA, and the route all agree about that state — you do not re-author sample data per surface, and you trust the green. When the data layer is tested against a real database, a passing repo test means the SQL and the migration genuinely work — you are not shipping on a belief. When time and IDs are injected, a green run means the logic is right, not that you got lucky with the clock.&lt;/p&gt;
&lt;p&gt;That trust is, once again, the velocity. A suite you can believe is one you can move behind quickly; a suite full of comfortable fictions is one you have to re-verify by hand, which is the coherence tax this whole series is about removing.&lt;/p&gt;
&lt;p&gt;And the infrastructure that buys this trust — fixtures-as-vocabulary, throwaway testcontainers, the FE-1 gate — is cheap to stand up now, while the surface area is small, and only gets more expensive to retrofit as the system grows.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is one boundary left that drifts harder than any database: a language model, which will cheerfully invent a value, a type, or a malformed shape. &lt;a href=&quot;http://localhost/blog/are-we-drifting-13-ai-structured-output&quot;&gt;Part 13: AI Output Is Untrusted Input&lt;/a&gt; treats AI output as exactly what it is — untrusted input — and shows how the vocabularies from earlier parts become the contract a model is forced to conform to.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 11: The FE↔BE Mirror</title><link>http://localhost/blog/are-we-drifting-11-the-fe-be-mirror/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-11-the-fe-be-mirror/</guid><description>The deepest coupling to keep from drifting is between the two halves of the product. The answer is a shared governance model: the same triple — structure, enforcement, one doc — mirrored across frontend and backend, honest about where the mirror doesn&apos;t hold.

</description><pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-11-the-febe-mirror&quot;&gt;Are We Drifting? — Part 11: The FE↔BE Mirror&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;The series has found one shape governing values, models, state, operations, and agents. This part turns it on the largest seam of all — the one between the frontend and the backend — and shows that even the &lt;em&gt;way the two stacks are governed&lt;/em&gt; is the same shape, mirrored.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Frontend and backend are usually built by different people, in different languages, with different instincts. Left alone, their &lt;em&gt;governance&lt;/em&gt; drifts as surely as their code: the frontend grows a rich culture of lint rules and structural conventions; the backend grows a different one; and an engineer who knows one stack arrives at the other as a stranger. The monorepo becomes two philosophies sharing a git remote.&lt;/p&gt;
&lt;p&gt;So the question here is not about a single value or model. It is: do the two halves of the product share &lt;em&gt;one&lt;/em&gt; model of what “governed” means — or two?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-shared-model-a-governed-sub-system&quot;&gt;The shared model: a Governed Sub-System&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Both stacks are built on the same unit. Every load-bearing architectural rule is a &lt;strong&gt;Governed Sub-System (GSS)&lt;/strong&gt; — a triple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Structure&lt;/strong&gt; — the canonical shape (a folder/file/type/proto convention) that names the right answer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforcement&lt;/strong&gt; — the load-bearing leg: a compile check, a lint rule, or a coverage script that fails the build. &lt;em&gt;Without it, the structure is a wish.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt; — one canonical page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the same &lt;code dir=&quot;auto&quot;&gt;source → projection → gate&lt;/code&gt; idea, applied to architecture itself: the structure is the declared intent, the enforcement is the gate, the doc is the human-readable projection. Both stacks also share a &lt;strong&gt;maturity ladder&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;compile-enforced → lint-error → coverage-script → warn (scoreboard) → convention&lt;/code&gt; — so every rule’s &lt;em&gt;real&lt;/em&gt; status is legible, not assumed. And both prefer guardrails that are &lt;strong&gt;opt-in by structure&lt;/strong&gt;: the rule self-targets by a convention and stays dormant until you adopt the shape.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-mirror-row-by-row&quot;&gt;The mirror, row by row&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the coupling made literal. Each row is one governance concept; the cells are how each stack instantiates it.&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Governance concept&lt;/th&gt;&lt;th&gt;Frontend&lt;/th&gt;&lt;th&gt;Backend&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Layering &amp;#x26; responsibility&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Feature Layering — Screen / Route / Controller / Flow / Runtime&lt;/td&gt;&lt;td&gt;Domain Layering — handler/repo co-located per domain, no cross-domain imports&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Data-source seam&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Controller / Data-source seam (&lt;code dir=&quot;auto&quot;&gt;controller.{fixtures,local,db}&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Repo Interface Seam — handler depends on a repo &lt;em&gt;interface&lt;/em&gt;, not &lt;code dir=&quot;auto&quot;&gt;pgx&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Exhaustive outcomes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;UI State System — &lt;code dir=&quot;auto&quot;&gt;matchState&lt;/code&gt; over &lt;code dir=&quot;auto&quot;&gt;ScreenState&lt;/code&gt; (compile + lint)&lt;/td&gt;&lt;td&gt;Typed Error Model — &lt;code dir=&quot;auto&quot;&gt;connect.NewError&lt;/code&gt; codes, no bare &lt;code dir=&quot;auto&quot;&gt;fmt.Errorf&lt;/code&gt; from a handler&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;No raw values / single source&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Design Tokens — semantic tokens, no raw &lt;code dir=&quot;auto&quot;&gt;oklch&lt;/code&gt;/hex&lt;/td&gt;&lt;td&gt;Structured Observability — key-value &lt;code dir=&quot;auto&quot;&gt;slog&lt;/code&gt;, no string interpolation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Generated indirection&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;feature registry — &lt;code dir=&quot;auto&quot;&gt;generate-features&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;features.generated.ts&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;--check&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Contract Integrity (&lt;code dir=&quot;auto&quot;&gt;buf&lt;/code&gt;) + generated-interface compile assertion&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Canonical test data&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Fixture Discipline (FE-1) — &lt;code dir=&quot;auto&quot;&gt;defineFixtures&lt;/code&gt; + registry&lt;/td&gt;&lt;td&gt;Migration Discipline — append-only &lt;code dir=&quot;auto&quot;&gt;*.sql&lt;/code&gt; + real-Postgres repo tests&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read any row left-to-right and the &lt;em&gt;same idea&lt;/em&gt; appears in two dialects. “Exhaustively handle every outcome” is &lt;code dir=&quot;auto&quot;&gt;matchState&lt;/code&gt; on the frontend and a typed error model on the backend. “Never write a raw value; use the single source” is design tokens on the frontend and structured &lt;code dir=&quot;auto&quot;&gt;slog&lt;/code&gt; on the backend. The concepts are coupled by design — which is what lets someone who has internalized one stack read the other.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-mirror-doesnt-hold&quot;&gt;Where the mirror doesn’t hold&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A forced mirror would itself be a drift between the docs and reality, so the asymmetries are part of the map:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend-only.&lt;/strong&gt; The rendering guardrails — rich skeletons, motion tokens, design tokens as pixels, the icon catalog — and the Storybook + Playwright workbench have no backend analogue. The backend has no rendering surface and no “agent eyes” to screenshot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend-only.&lt;/strong&gt; Contract Integrity, Migration Discipline, and Middleware Centralization are backend realities (a wire contract, append-only schema, central HTTP middleware) with only a thin frontend echo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforcement depth differs, for a real reason.&lt;/strong&gt; The frontend runs many custom ESLint rules because ESLint is &lt;strong&gt;file-granular and syntactic&lt;/strong&gt; — cheap and precise to extend. The backend leans on &lt;code dir=&quot;auto&quot;&gt;buf&lt;/code&gt;, the compiler, and a growing wall of CI scripts (&lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;check-mocks&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;check-sqlc&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;check-sql-scope&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;check-connect-errors&lt;/code&gt;, migration checks) because Go’s equivalents are &lt;strong&gt;package-granular and often need type information&lt;/strong&gt; — closer to a day of work than an hour. So a few backend rules are CI scripts or partly lean on review where the frontend would have a bespoke linter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mirror is a design goal, not a straitjacket. Naming the &lt;code dir=&quot;auto&quot;&gt;—&lt;/code&gt; cells keeps the map true to the territory.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-screen-six-guardrails&quot;&gt;One screen, six guardrails&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The abstract case for governance is easy to nod along to. Here is the concrete one — a single frontend screen, &lt;em&gt;before&lt;/em&gt;, with six independent drift modes, each the kind of thing that slips through review:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// BEFORE — six latent drift modes in one file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { coachRosterFixtures } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./screen.fixtures&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;// FE-1: fixture imported into a screen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CoachRosterScreen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uiState&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {    &lt;/span&gt;&lt;span&gt;// a parallel uiState prop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;{ background: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;#0f0f17&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;                // a raw hex color&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;matchState&lt;/span&gt;&lt;span&gt;(state&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;lm-skeleton&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// an anonymous loading bar&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Rows&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// missing empty + error arms                      // a non-exhaustive match&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// &amp;#x3C;Suspense fallback={null}&gt;                              // a blank first paint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Six drift modes. Six named rules. Each one is a lint error or compile failure — not a code-review note:&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift mode&lt;/th&gt;&lt;th&gt;Rule&lt;/th&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Fixture imported into a screen&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Parallel &lt;code dir=&quot;auto&quot;&gt;uiState&lt;/code&gt; prop on a screen&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-uistate-screen-prop&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Raw hex color &lt;code dir=&quot;auto&quot;&gt;#0f0f17&lt;/code&gt; in component code&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-raw-hex-color&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error (ambient)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Anonymous skeleton div (no &lt;code dir=&quot;auto&quot;&gt;skeleton.tsx&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;skeleton-composition&lt;/code&gt; rule&lt;/td&gt;&lt;td&gt;lint-error (opt-in)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Non-exhaustive &lt;code dir=&quot;auto&quot;&gt;matchState&lt;/code&gt; (missing arms)&lt;/td&gt;&lt;td&gt;TypeScript exhaustiveness check&lt;/td&gt;&lt;td&gt;compile-enforced&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;Suspense fallback={null}&gt;&lt;/code&gt; in router&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-null-suspense-fallback&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The &lt;em&gt;after&lt;/em&gt; version is not “tidier by convention” — it is the only version that compiles and lints. Six coherence checks a reviewer would otherwise have to perform by eye, performed by the build instead.&lt;/p&gt;
&lt;p&gt;The backend’s equivalent table:&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift mode&lt;/th&gt;&lt;th&gt;Rule&lt;/th&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Handler drifts from generated interface&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ ServiceHandler = (*Service)(nil)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;compile-enforced&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Proto contract breaks without escalation&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;buf lint&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error (CI)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;connect.NewError&lt;/code&gt; called outside the error adapter&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-connect-errors&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CI script&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Generated DB models drift from SQL&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-sqlc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CI script&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Generated mocks drift from interfaces&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-mocks&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CI script&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vocabulary &lt;code dir=&quot;auto&quot;&gt;.gen.go&lt;/code&gt; drifts from YAML manifest&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-vocab&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CI script&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Migration file edited after applied&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-migrations.sh&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CI script&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;That is the whole argument of the series in two tables: the structure plus the gate is what lets a large, AI-generated diff be &lt;em&gt;trusted&lt;/em&gt;, because the ways it could drift are the ways the build already refuses — on both stacks.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The check this removes operates at the level of &lt;em&gt;people&lt;/em&gt;: the cost of every engineer (and every agent) having to learn two unrelated governance cultures, and the cost of reviewers manually enforcing consistency on both.&lt;/p&gt;
&lt;p&gt;A shared model collapses that. Learn the GSS triple and the maturity ladder once, and both stacks are legible. An agent briefed on “structure + enforcement + one doc” applies it on either side. And the matrix itself is governed like everything else — adding a new sub-system on either stack &lt;em&gt;adds a row&lt;/em&gt; as its final step, so the cross-stack map cannot silently drift from the rules it describes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The next parts turn to testing — how fixtures, fakes, and real-Postgres containers keep tests from drifting away from production — then to AI output as an untrusted boundary, and to a running inventory of the wall of gates and where the pattern goes next.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 10: The Agent OS</title><link>http://localhost/blog/are-we-drifting-10-the-agent-os/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-10-the-agent-os/</guid><description>If you hand-maintain a Claude agent file and a Cursor rule for the same role, they drift. So the role is declared once and projected onto every framework, the tier is provider-agnostic, and a drift gate fails CI when any rendered copy falls out of sync.

</description><pubDate>Thu, 11 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-10-the-agent-os&quot;&gt;Are We Drifting? — Part 10: The Agent OS&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-9-one-operation-three-interfaces&quot;&gt;Part 9: One Operation, Three Interfaces&lt;/a&gt; projected one operation onto three transports. This part does the same thing one altitude higher: it projects one &lt;em&gt;agent&lt;/em&gt; onto every AI framework that runs it — and keeps the fleet’s shared knowledge from forking.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Agents are now contributors. They have roles, instructions, tools, and tiers. And those roles have to exist in more than one place: Claude Code wants an agent file with its frontmatter shape; Cursor wants a rule file with a different frontmatter shape; the docs want a human-readable page describing each role.&lt;/p&gt;
&lt;p&gt;Hand-maintain those and you have the oldest drift in the book. Someone tightens an agent’s instructions in the Claude file and forgets the Cursor rule. The docs describe a tool the agent no longer has. The same “principal architect” behaves like two different colleagues depending on which tool you opened. Worse, the &lt;em&gt;shared&lt;/em&gt; behavior — how juniors commit, how principals review — gets copy-pasted into every agent and then edited in some but not others, so the fleet’s common knowledge quietly forks.&lt;/p&gt;
&lt;p&gt;The question is the series’ question, pointed at the agents themselves: is each role one definition, or many copies pretending to be one?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-source&quot;&gt;One source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every agent is one folder with one canonical &lt;code dir=&quot;auto&quot;&gt;manifest.json&lt;/code&gt; (plus a hand-authored &lt;code dir=&quot;auto&quot;&gt;prompt.md&lt;/code&gt;, and an optional &lt;code dir=&quot;auto&quot;&gt;composition&lt;/code&gt; of shared role-blocks). That manifest is the single definition of the role: its name, description, tools, tier, and which shared behaviors it composes.&lt;/p&gt;
&lt;p&gt;Shared behavior lives in &lt;strong&gt;role-blocks&lt;/strong&gt; — fragments like &lt;code dir=&quot;auto&quot;&gt;principal/advisory-scope&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;principal/review-checklist&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;base/commit-skill-pointer&lt;/code&gt; — that many agents pull in by reference. An agent’s assembled prompt is its own &lt;code dir=&quot;auto&quot;&gt;prompt.md&lt;/code&gt; spliced into the ordered blocks it composes. Change a role-block once, and every agent that composes it changes with it. That is the “shared knowledge mesh”: common discipline declared in one place, woven into many roles, never copy-pasted.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;many-projections&quot;&gt;Many projections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A package (&lt;code dir=&quot;auto&quot;&gt;agent-registry&lt;/code&gt;) walks the manifests and renders each through every registered adapter:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Adapter&lt;/th&gt;&lt;th&gt;Destination&lt;/th&gt;&lt;th&gt;Render shape&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.claude/agents/&amp;#x3C;name&gt;.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Claude Code frontmatter + a pointer to the canonical prompt&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.cursor/rules/&amp;#x3C;name&gt;.mdc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Cursor frontmatter + a reference to the canonical prompt&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;docs-role&lt;/code&gt;&lt;/td&gt;&lt;td&gt;the agent’s &lt;code dir=&quot;auto&quot;&gt;role.mdx&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a Docusaurus role page&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;docs-table&lt;/code&gt;&lt;/td&gt;&lt;td&gt;the registry table&lt;/td&gt;&lt;td&gt;one row per agent&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The adapters exist &lt;em&gt;because&lt;/em&gt; the frameworks genuinely differ — Claude Code needs &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;tools&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;model&lt;/code&gt;, Cursor needs &lt;code dir=&quot;auto&quot;&gt;globs&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;alwaysApply&lt;/code&gt; — which is exactly why you cannot symlink one file and call it done. Each framework gets the shape it expects, all from one manifest.&lt;/p&gt;
&lt;p&gt;The render loop is &lt;strong&gt;idempotent&lt;/strong&gt;: it reads the existing destination, renders fresh content, and only writes if the bytes differ. Running it is a no-op when nothing changed — which is what makes the next part (the gate) clean.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;provider-agnostic-tiers-name-and-wire-again&quot;&gt;Provider-agnostic tiers: name and wire, again&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; split from the very first vocabulary, reappearing at the top of the stack.&lt;/p&gt;
&lt;p&gt;Every model-driven agent carries a &lt;code dir=&quot;auto&quot;&gt;tier&lt;/code&gt; — &lt;code dir=&quot;auto&quot;&gt;E1&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;E2&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;E3&lt;/code&gt; — not a model name. The tier is the &lt;em&gt;canonical identity&lt;/em&gt;. A separate &lt;code dir=&quot;auto&quot;&gt;agent-runtime.json&lt;/code&gt; resolves a tier to a concrete model (Claude Sonnet, an OpenAI model, whatever) &lt;strong&gt;at dispatch time&lt;/strong&gt;. The tier is the &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;; the concrete model is the &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; form; the binding is deferred and declared in one place.&lt;/p&gt;
&lt;p&gt;So “what model does the principal architect use?” has the same answer-shape as “what string does &lt;code dir=&quot;auto&quot;&gt;Ready&lt;/code&gt; serialize to?” — it is a resolved projection of a declared identity, not a literal scattered across nineteen files. Swap the model behind &lt;code dir=&quot;auto&quot;&gt;E2&lt;/code&gt; and every E2 agent moves together, across every provider, without touching a single agent manifest.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-drift-gate&quot;&gt;The drift gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The render loop has a check mode. &lt;code dir=&quot;auto&quot;&gt;make agents-check&lt;/code&gt; runs the adapters in dry-run and &lt;strong&gt;exits non-zero if any destination differs from what its manifest would generate.&lt;/strong&gt; It runs in CI.&lt;/p&gt;
&lt;p&gt;So a &lt;code dir=&quot;auto&quot;&gt;.claude/agents/*.md&lt;/code&gt; that was hand-edited, a Cursor rule left stale after a manifest change, an assembled prompt that was never regenerated after a role-block edit — each fails the build. The only way to change how an agent behaves in any framework is to change the manifest or a role-block and re-run the sync. One source, many rendered projections, a gate that refuses drift between them. The exact shape of &lt;a href=&quot;http://localhost/blog/are-we-drifting-3-buildmere&quot;&gt;Part 3: buildmere, a Codegen Kernel&lt;/a&gt;, applied to the colleagues instead of the code.&lt;/p&gt;
&lt;p&gt;There are four failure modes the gate is built to catch, and each one tells you something about where the real discipline lives:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Symptom&lt;/th&gt;&lt;th&gt;What drifted&lt;/th&gt;&lt;th&gt;Fix&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.claude/agents/&amp;#x3C;name&gt;.md&lt;/code&gt; body is stale&lt;/td&gt;&lt;td&gt;Manifest or role-block changed; sync not re-run&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make sync-agents&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;prompt.assembled.md&lt;/code&gt; missing&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;composition&lt;/code&gt; key added; &lt;code dir=&quot;auto&quot;&gt;make sync-agents&lt;/code&gt; never re-run&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make sync-agents&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Orphaned &lt;code dir=&quot;auto&quot;&gt;.claude/agents/&amp;#x3C;name&gt;.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Agent folder deleted without pruning&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make sync-agents PRUNE=1&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cursor rule out of sync&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.cursor/rules/&amp;#x3C;name&gt;.mdc&lt;/code&gt; hand-edited after manifest change&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make sync-agents&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Every row in our failure-mode table ends the same way: &lt;code dir=&quot;auto&quot;&gt;make sync-agents&lt;/code&gt;. The fix for drift is to regenerate, never to hand-patch — and that single recovery path is itself the point.&lt;/p&gt;
&lt;p&gt;That is the point of idempotency — the fix is always the same command, because the sync is always a deterministic function of the manifests.&lt;/p&gt;
&lt;p&gt;The section above named four adapters because four is what the system conceptually does — assemble, emit to Claude, emit to Cursor, produce docs. The registry runs six:&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Adapter&lt;/th&gt;&lt;th&gt;Destination&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;prompt-assembler&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;agent&gt;/prompt.assembled.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.claude/agents/&amp;#x3C;name&gt;.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.cursor/rules/&amp;#x3C;name&gt;.mdc&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;docs-role&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;agent&gt;/role.mdx&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;agent-category&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;agent&gt;/_category_.json&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;docs-table&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;apps/docs/docs/ai/agents/agent-definitions.mdx&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;agent-category&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;docs-table&lt;/code&gt; are the quiet infrastructure that makes the docs self-maintaining — every agent that gets a manifest gets a Docusaurus sidebar entry and a registry row, automatically, without a docs PR.&lt;/p&gt;
&lt;p&gt;Scaffolding a new agent is two commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;agents-new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NAME=&amp;#x3C;kebab-case-name&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# → apps/docs/docs/ai/agents/&amp;#x3C;name&gt;/{manifest.json, prompt.md}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sync-agents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# → .claude/agents/&amp;#x3C;name&gt;.md, .cursor/rules/&amp;#x3C;name&gt;.mdc, role.mdx, _category_.json, registry row&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Write the manifest and the prompt, run the sync, and the agent exists in every framework simultaneously.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The gate won’t pass until the sync has run — which means you cannot forget the sync and ship anyway. The broken CI is the reminder.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The next adapters extend the same registry to two more destinations: &lt;code dir=&quot;auto&quot;&gt;agents-md-platform.mjs&lt;/code&gt; (root &lt;code dir=&quot;auto&quot;&gt;AGENTS.md&lt;/code&gt;) and &lt;code dir=&quot;auto&quot;&gt;cursor-rules-platform.mjs&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;.cursor/rules/_platform.mdc&lt;/code&gt;). Today &lt;code dir=&quot;auto&quot;&gt;AGENTS.md&lt;/code&gt; is a pointer file, and the platform layer is where this goes next: every existing agent renders into those destinations for free — adding a new framework adapter is a new file and a registration line, not a sweep across nineteen manifests.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The check this removes is “are all the framework copies of this role still saying the same thing?” — multiplied by nineteen agents and two skills, across two frameworks, plus docs.&lt;/p&gt;
&lt;p&gt;Declaring the role once collapses that to a single edit. Improve a principal’s review checklist in one role-block; every principal in every framework updates, and &lt;code dir=&quot;auto&quot;&gt;agents-check&lt;/code&gt; proves they did. Onboard a new framework by writing one adapter, and every existing agent renders into it for free. Re-point a tier at a better model in one file, and the whole fleet follows.&lt;/p&gt;
&lt;p&gt;This is the deepest expression of the economic inversion from &lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt;: the contributors themselves are governed by a manifest, so improving how the whole fleet works is a one-line change with a gate behind it — not a careful, error-prone sweep across every tool’s idea of every role.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We have now found the same shape governing values, models, state, operations, and agents. &lt;a href=&quot;http://localhost/blog/are-we-drifting-11-the-fe-be-mirror&quot;&gt;Part 11: The FE↔BE Mirror&lt;/a&gt; holds the two halves of the product up against each other — frontend and backend — and shows that even the &lt;em&gt;governance itself&lt;/em&gt; is mirrored, with the asymmetries named honestly.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 9: One Operation, Three Interfaces</title><link>http://localhost/blog/are-we-drifting-9-one-operation-three-interfaces/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-9-one-operation-three-interfaces/</guid><description>A capability your app calls, your agents call, and you call from the terminal is one capability with three faces. Define it once; project it onto every transport; let a contract test refuse any face that drifts from the others.

</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-9-one-operation-three-interfaces&quot;&gt;Are We Drifting? — Part 9: One Operation, Three Interfaces&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;The data parts are behind us. The shape is not. &lt;a href=&quot;http://localhost/blog/are-we-drifting-2-the-tagged-vocabulary&quot; title=&quot;The Tagged Vocabulary&quot;&gt;Parts 2–8&lt;/a&gt; showed &lt;em&gt;one source → many projections → drift gates&lt;/em&gt; governing values, models, and state. Now watch it govern &lt;strong&gt;behavior&lt;/strong&gt; — the operations a service exposes.&lt;/p&gt;
&lt;figure&gt; &lt;svg viewBox=&quot;0 0 788.3 200&quot; role=&quot;img&quot; aria-label=&quot;defineOperation(input, output), generating REST endpoint, MCP tool, CLI command, kept honest by defineMcpServerContract&quot;&gt; &lt;defs&gt; &lt;marker id=&quot;shape-arrow&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt; &lt;path d=&quot;M 0 1 L 9 5 L 0 9&quot;&gt;&lt;/path&gt; &lt;/marker&gt; &lt;/defs&gt; &lt;!-- source → projections (generates) --&gt; &lt;line x1=&quot;257&quot; y1=&quot;100&quot; x2=&quot;349&quot; y2=&quot;56&quot; marker-end=&quot;url(#shape-arrow)&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;257&quot; y1=&quot;100&quot; x2=&quot;349&quot; y2=&quot;100&quot; marker-end=&quot;url(#shape-arrow)&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;257&quot; y1=&quot;100&quot; x2=&quot;349&quot; y2=&quot;144&quot; marker-end=&quot;url(#shape-arrow)&quot;&gt;&lt;/line&gt; &lt;!-- projections → gate (checked by) --&gt; &lt;line x1=&quot;489&quot; y1=&quot;56&quot; x2=&quot;581&quot; y2=&quot;100&quot; marker-end=&quot;url(#shape-arrow)&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;489&quot; y1=&quot;100&quot; x2=&quot;581&quot; y2=&quot;100&quot; marker-end=&quot;url(#shape-arrow)&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;489&quot; y1=&quot;144&quot; x2=&quot;581&quot; y2=&quot;100&quot; marker-end=&quot;url(#shape-arrow)&quot;&gt;&lt;/line&gt; &lt;!-- edge labels --&gt; &lt;text x=&quot;303&quot; y=&quot;90&quot;&gt;generates&lt;/text&gt; &lt;text x=&quot;535&quot; y=&quot;90&quot;&gt;gated by&lt;/text&gt; &lt;!-- source --&gt; &lt;g&gt; &lt;rect x=&quot;16&quot; y=&quot;74&quot; width=&quot;241&quot; height=&quot;52&quot; rx=&quot;8&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;136.5&quot; y=&quot;94&quot;&gt;ONE SOURCE&lt;/text&gt; &lt;text x=&quot;136.5&quot; y=&quot;114&quot;&gt;defineOperation(input, output)&lt;/text&gt; &lt;/g&gt; &lt;!-- projections --&gt; &lt;g&gt; &lt;rect x=&quot;349&quot; y=&quot;40&quot; width=&quot;140&quot; height=&quot;32&quot; rx=&quot;6&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;419&quot; y=&quot;60&quot;&gt;REST endpoint&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;349&quot; y=&quot;84&quot; width=&quot;140&quot; height=&quot;32&quot; rx=&quot;6&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;419&quot; y=&quot;104&quot;&gt;MCP tool&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;rect x=&quot;349&quot; y=&quot;128&quot; width=&quot;140&quot; height=&quot;32&quot; rx=&quot;6&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;419&quot; y=&quot;148&quot;&gt;CLI command&lt;/text&gt; &lt;/g&gt; &lt;text x=&quot;419&quot; y=&quot;28&quot;&gt;MANY PROJECTIONS&lt;/text&gt; &lt;!-- gate --&gt; &lt;g&gt; &lt;rect x=&quot;581&quot; y=&quot;74&quot; width=&quot;191.29999999999998&quot; height=&quot;52&quot; rx=&quot;8&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;676.65&quot; y=&quot;94&quot;&gt;DRIFT GATE&lt;/text&gt; &lt;text x=&quot;676.65&quot; y=&quot;114&quot;&gt;defineMcpServerContract&lt;/text&gt; &lt;/g&gt; &lt;/svg&gt; &lt;figcaption&gt;The same shape, at the operations layer: one handler, three transports, one contract test.&lt;/figcaption&gt; &lt;/figure&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A modern backend capability has at least three audiences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;app&lt;/strong&gt;, which wants a REST endpoint;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;agents&lt;/strong&gt;, which want an MCP tool they can discover and call;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;humans&lt;/strong&gt; (and a fallback for when MCP is flaky), who want a CLI command.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lazy version writes three surfaces. A REST handler here, an MCP tool registration there, a CLI subcommand somewhere else — each with its own copy of the input shape, its own validation, its own idea of what the operation is called.&lt;/p&gt;
&lt;p&gt;Then they drift. A field is added to the REST body but not the MCP tool’s schema. A new operation ships as an MCP tool but was never wired into REST, so the app cannot reach it and it never appears in the operation listing. The CLI validates differently from the server. Three faces of one capability, slowly disagreeing about what the capability &lt;em&gt;is.&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-source&quot;&gt;One source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The fix is to define the operation once — its input schema, its output schema, and its handler — and treat every transport as a projection of that one definition. Our &lt;code dir=&quot;auto&quot;&gt;op-server&lt;/code&gt; package does exactly this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defineOperation&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;run.record&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;input: &lt;/span&gt;&lt;span&gt;RunRecordInput&lt;/span&gt;&lt;span&gt;,    &lt;/span&gt;&lt;span&gt;// a Zod schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;output: &lt;/span&gt;&lt;span&gt;RunStateSchema&lt;/span&gt;&lt;span&gt;,   &lt;/span&gt;&lt;span&gt;// a Zod schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;/* the one implementation */&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The operation is the source. The input and output &lt;em&gt;are&lt;/em&gt; schemas, not prose — the same “validation is declared, not hand-written” move from the config part, now at the boundary of an operation.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;many-projections&quot;&gt;Many projections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A service collects its operations into one registry, and the transports are plugins that consume that &lt;strong&gt;same array&lt;/strong&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;createOpServer&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;operations&lt;/span&gt;&lt;span&gt; })       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// the registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;createRestPlugin&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;/* … */&lt;/span&gt;&lt;span&gt; })        &lt;/span&gt;&lt;span&gt;// → REST endpoints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;createMcpServer&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;/* … */&lt;/span&gt;&lt;span&gt; })         &lt;/span&gt;&lt;span&gt;// → MCP tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// + a CLI plugin                     // → CLI commands&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;REST and MCP both read the same &lt;code dir=&quot;auto&quot;&gt;operations&lt;/code&gt;. There is one handler behind all three faces. This is what “all tools are MCP-driven” means in practice: every backlog and devportal-state operation is automatically an MCP tool &lt;em&gt;because it is in the registry&lt;/em&gt; — agents reach the repo’s live state through the very same operations the app calls over REST and a human calls from the terminal. You do not build an MCP surface; you declare operations, and the MCP surface is a projection of them.&lt;/p&gt;
&lt;p&gt;A consequence worth stating: a tool registered only on one transport can never be reached on the others, and won’t appear in the registry’s own &lt;code dir=&quot;auto&quot;&gt;operations.list&lt;/code&gt; introspection. The single registry is what keeps the three faces from being three different APIs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-drift-gate-a-contract-test-against-the-real-sdk&quot;&gt;The drift gate: a contract test against the real SDK&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the gate, and it is a good one because it was paid for in production.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;op-server&lt;/code&gt; ships a test contract — &lt;code dir=&quot;auto&quot;&gt;defineMcpServerContract&lt;/code&gt; — that &lt;strong&gt;boots the real MCP server against the live SDK&lt;/strong&gt; and asserts that the registered tool surface matches the operations registry &lt;em&gt;exactly.&lt;/em&gt; Every operation’s input is run through the same &lt;code dir=&quot;auto&quot;&gt;extractShape&lt;/code&gt; the server uses, so the SDK receives a proper schema. A tool that was hand-registered outside the registry shows up as an “extra” and fails the test. An operation whose schema the SDK would reject fails here, at unit-test time, instead of at boot.&lt;/p&gt;
&lt;p&gt;Why this specific gate exists: hand-calling the SDK’s &lt;code dir=&quot;auto&quot;&gt;registerTool&lt;/code&gt; with a JSON-Schema-shaped object — instead of going through the registry — passes lint and type-check but &lt;strong&gt;crashes the SDK at boot&lt;/strong&gt;, taking the entire MCP server down. That regression happened in production once (the devportal-state projection tool). So the rule is now structural and enforced:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not call &lt;code dir=&quot;auto&quot;&gt;registerTool&lt;/code&gt; directly. Add an entry to the operations registry and let the server register it. Living outside the registry means living outside the safety net.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The registry is the single source; the contract test is the gate that refuses any tool that tried to exist outside it. Same shape, again.&lt;/p&gt;
&lt;p&gt;The three-interface picture has a fourth failure mode that only shows up in long Claude Code sessions: MCP is not bulletproof.&lt;/p&gt;
&lt;p&gt;Tools disappear from the parent agent’s registry while subagents keep working.&lt;/p&gt;
&lt;p&gt;A tool call hangs.&lt;/p&gt;
&lt;p&gt;When that happens, the question is not whether the registry kept the three faces in sync — it did — but whether a CLI fallback exists so an agent can recover without restarting. The answer is not symmetric:&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Operation surface&lt;/th&gt;&lt;th&gt;MCP transport&lt;/th&gt;&lt;th&gt;CLI fallback&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Backlog ops (&lt;code dir=&quot;auto&quot;&gt;backlog-mcp&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;mcp__backlog__*&lt;/code&gt; tools&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;pnpm liftmere backlog ...&lt;/code&gt; — every tool has an exact equivalent, same handlers, same events&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Devportal-state ops (&lt;code dir=&quot;auto&quot;&gt;devportal-state-mcp&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;mcp__devportal_state__*&lt;/code&gt; tools&lt;/td&gt;&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt; — recovery is &lt;code dir=&quot;auto&quot;&gt;/mcp&lt;/code&gt; reconnect or delegate to a subagent&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;The single registry guarantees the three transport &lt;em&gt;faces&lt;/em&gt; agree on input shape. It does not guarantee all three transports exist for every operation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A runbook that says “if MCP hangs, use the CLI” is correct for backlog operations and wrong for devportal-state operations.&lt;/p&gt;
&lt;p&gt;That asymmetry is real: devportal-state has no CLI fallback today, so its recovery path is &lt;code dir=&quot;auto&quot;&gt;/mcp&lt;/code&gt; reconnect or delegation to a subagent. Where this goes next is parity — a CLI projection for devportal-state operations, so every operation has all three faces and the runbook holds everywhere.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The coherence check this removes is “did I update all three transports, and do they still agree?”&lt;/p&gt;
&lt;p&gt;With operations as the source, you add a capability &lt;em&gt;once.&lt;/em&gt; The moment it is in the registry it is a REST endpoint the app can call, an MCP tool an agent can discover, and a CLI command a human can run — with one validated input shape and one handler. You cannot ship the half-drifted state where a capability is reachable one way but not another, because the contract test boots the real surface and compares it to the registry.&lt;/p&gt;
&lt;p&gt;That is a large multiplier for agentic work specifically: an agent gains a new ability the instant an operation is defined, and the ability it gains is provably the same one the app uses. No separate “expose this to the agent” step, no separate schema for the agent to drift against.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If one operation can project onto three transports, one &lt;em&gt;agent definition&lt;/em&gt; can project onto every AI tool that runs it. &lt;a href=&quot;http://localhost/blog/are-we-drifting-10-the-agent-os&quot;&gt;Part 10: The Agent OS&lt;/a&gt; climbs to the workflow layer: the Agent OS, where a single manifest becomes a Claude Code agent, a Cursor rule, and a provider-agnostic execution tier — kept in sync by the same kind of gate.&lt;/p&gt;
&lt;p&gt;And because operation inputs are already Zod schemas, there is one more face waiting: a &lt;em&gt;frontend form&lt;/em&gt; generated from an operation’s input schema. Where this goes next is the fourth projection — a frontend form generated from the same operation schema.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 8: Projected Truth</title><link>http://localhost/blog/are-we-drifting-8-projected-truth/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-8-projected-truth/</guid><description>A dashboard table is a copy of the truth, and copies drift. Unless the copy is disposable — rebuilt on demand from an append-only log of facts. Then the projection can never win an argument with reality, and new views are free.

</description><pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-8-projected-truth&quot;&gt;Are We Drifting? — Part 8: Projected Truth&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-7-models-at-boundaries&quot;&gt;Part 7: Models at Boundaries&lt;/a&gt; named &lt;strong&gt;events&lt;/strong&gt; as the boundary that records facts. This part is about what you can build once facts are the source of truth — and why it is the most complete answer to drift we have found.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every useful screen is backed by a read model: a denormalized, query-shaped view of state. A kanban board. A list of active runs. A roll-up of which pull requests touch which tickets.&lt;/p&gt;
&lt;p&gt;A read model is, by definition, a &lt;em&gt;copy&lt;/em&gt; of truth arranged for fast reading. And the first law of this series is that copies drift. The board says a ticket is in review; the underlying state moved on; a consumer that updates the board missed an event, or processed one twice, or crashed halfway. Now the view and reality disagree, and you are debugging &lt;em&gt;which one is lying.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The usual fix is more careful update logic. That just makes the copy drift more slowly. The real fix is structural: make the copy &lt;strong&gt;disposable&lt;/strong&gt;, so it can never win an argument with the truth.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;facts-not-state&quot;&gt;Facts, not state&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Start from the right source. A command can fail; an &lt;em&gt;event&lt;/em&gt; already happened. So the durable record is not “the current state” — it is the append-only sequence of facts that produced the current state.&lt;/p&gt;
&lt;p&gt;In our orchestration plane, that record is a literal append-only log: monthly JSONL files under &lt;code dir=&quot;auto&quot;&gt;.devportal/events/&lt;/code&gt;, written by a single sequencer so there is exactly one writer and one order. Every state change is a fact appended to it — &lt;code dir=&quot;auto&quot;&gt;run.recorded&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;pr.synced&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;review.summarized&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ticket.transitioned&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;workspace.created&lt;/code&gt;. Each fact is validated against a &lt;strong&gt;Zod event schema&lt;/strong&gt; before it is written, and those schemas are &lt;strong&gt;forward-only&lt;/strong&gt;: you add new event kinds and new optional fields, you never rewrite the meaning of a fact already on disk. (That is the append-only discipline from the enum part, applied to history itself.)&lt;/p&gt;
&lt;p&gt;Two properties make the log trustworthy as a source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single writer, one order.&lt;/strong&gt; The sequencer is the only thing that appends, so there is a single, total order of facts — no last-write-wins races.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idempotent appends.&lt;/strong&gt; Each publisher mints an idempotency key, and the sequencer dedups on it, so a retried publish does not become a duplicate fact.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The log is the truth. Nothing else is.&lt;/p&gt;
&lt;aside aria-label=&quot;Are we drifting here?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Are we drifting here?&lt;/p&gt;&lt;div&gt;&lt;p&gt;Both append-only disciplines — the event log and the Zod schemas — have machine enforcement; neither relies on human care.&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift scenario&lt;/th&gt;&lt;th&gt;Named gate&lt;/th&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Code appends directly to &lt;code dir=&quot;auto&quot;&gt;.devportal/events/&lt;/code&gt; without going through the sequencer&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-direct-event-log-append&lt;/code&gt; (ESLint, Node packages)&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A shipped SQL migration is edited, deleted, or renamed after it landed on &lt;code dir=&quot;auto&quot;&gt;main&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;scripts/check-migrations.sh&lt;/code&gt; — &lt;code dir=&quot;auto&quot;&gt;git diff --diff-filter=MDR&lt;/code&gt; vs. merge-base; runs in &lt;code dir=&quot;auto&quot;&gt;make lint-migrations&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;make lint-be&lt;/code&gt;&lt;/td&gt;&lt;td&gt;coverage-script + CI error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A SQL schema change breaks a running reader (DROP column, narrowed type, blocking constraint)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;scripts/squawk-migrations.sh&lt;/code&gt; + &lt;code dir=&quot;auto&quot;&gt;.squawk.toml&lt;/code&gt; — extracts only the &lt;code dir=&quot;auto&quot;&gt;-- +goose Up&lt;/code&gt; section and lints it for backwards-compat&lt;/td&gt;&lt;td&gt;coverage-script + CI error&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;check-migrations.sh&lt;/code&gt; fails with: &lt;em&gt;“Migration Discipline violation: shipped migration(s) were changed.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;The event-log rule and the migration gate enforce the same axiom from two directions: &lt;code dir=&quot;auto&quot;&gt;liftmere/no-direct-event-log-append&lt;/code&gt; keeps every write in-order through the one sequencer; &lt;code dir=&quot;auto&quot;&gt;check-migrations.sh&lt;/code&gt; keeps every schema change additive. Neither can be reset by “being more careful” — the CI job catches it or the build fails.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;projections-are-disposable-views&quot;&gt;Projections are disposable views&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Everything you read from is a &lt;strong&gt;projection&lt;/strong&gt; of that log: a Postgres database whose tables (&lt;code dir=&quot;auto&quot;&gt;tickets&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;runs&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;prs&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;reviews&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;runners&lt;/code&gt;) are &lt;em&gt;built by replaying the facts.&lt;/em&gt; Consumers — &lt;code dir=&quot;auto&quot;&gt;active-runs-projection&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kanban-projection&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;parent-lifecycle-mirror&lt;/code&gt; — subscribe to the log through a shared consumer framework, read forward from a checkpoint, and fold each fact into their tables.&lt;/p&gt;
&lt;p&gt;This is precisely the series’ shape, at the altitude of state-over-time:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;the event log (append-only facts)   -&gt;  the one source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Postgres projections (tables)        -&gt;  many generated views&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rebuild-from-log                     -&gt;  the drift gate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A projection is never the truth. It is a &lt;em&gt;view&lt;/em&gt; of the truth — the same sentence we used for generated code in &lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt;, now about data. And because it is only a view, it carries no authority of its own.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;rebuild-from-log-is-the-gate&quot;&gt;Rebuild-from-log is the gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the part that makes drift recoverable instead of catastrophic.&lt;/p&gt;
&lt;p&gt;Because every projection is a pure fold over the log, it can be &lt;strong&gt;dropped and rebuilt from the facts at any time.&lt;/strong&gt; The &lt;code dir=&quot;auto&quot;&gt;devportal_state_db_rebuildFromLog&lt;/code&gt; operation, run in full or incremental mode, hydrates the projection database from disk — a full rebuild from the start of history, or an incremental catch-up from a checkpoint.&lt;/p&gt;
&lt;p&gt;That single capability changes the failure mode completely. When a projection and the log disagree, you do not investigate which is right. The log is right, by definition; you rebuild the projection and the disagreement is gone. A corrupted read model is not a data-loss incident — it is a re-run. The consumer framework keeps durable checkpoints (&lt;code dir=&quot;auto&quot;&gt;consumer.offset_committed&lt;/code&gt; is itself a fact in the log), and distinguishes those from ephemeral liveness signals like heartbeats, so a restart resumes exactly where it left off without replaying what it already folded.&lt;/p&gt;
&lt;p&gt;Drift between truth and its views is not &lt;em&gt;prevented&lt;/em&gt; here so much as made &lt;em&gt;cheap to erase.&lt;/em&gt; Which, for a copy, is the strongest guarantee there is.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The check this removes is the one that haunts every cache and read model: &lt;em&gt;is this view actually consistent with reality, and if not, how do I fix it without losing data?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When the view is a rebuildable projection, that question stops being scary. You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add a new read model for free.&lt;/strong&gt; A new screen needs a new shape? Write a new consumer that folds the existing log into it, and replay history into the new projection. No backfill migration, no “where do I get the historical data” — the history is the log. Standing up a new projection is a replay, not a migration project: the economics invert, and a read model that used to cost a schema change plus a data backfill now costs the time it takes to fold the log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change a projection’s shape without fear.&lt;/strong&gt; Reshape the table, drop it, rebuild. The facts are untouched.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recover from a bad deploy by replaying,&lt;/strong&gt; not by archaeology.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New views become cheap because truth is one append-only log rather than &lt;em&gt;N&lt;/em&gt; mutable tables you must keep mutually consistent by hand. That is the velocity unlock from &lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt; in its purest form: the structure (an append-only log of validated facts) makes a whole class of expensive coherence work — keeping every read model in sync with reality — simply disappear.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-boundary-sits&quot;&gt;Where the boundary sits&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This pattern governs our &lt;strong&gt;orchestration plane&lt;/strong&gt; — runs, tickets, reviews, the devportal’s view of the fleet. It is the right tool there because that domain is intrinsically a stream of events about work happening over time.&lt;/p&gt;
&lt;p&gt;It is &lt;em&gt;not&lt;/em&gt; how the product’s own data works. Workouts, programs, and media are classic CRUD over Postgres, accessed through the repo seam from &lt;a href=&quot;http://localhost/blog/are-we-drifting-7-models-at-boundaries&quot;&gt;Part 7: Models at Boundaries&lt;/a&gt; — no event sourcing, because that domain does not need replayable history to do its job. Using the heavier pattern everywhere would be its own kind of drift: architecture that has wandered away from what the domain actually requires. We apply projected truth where facts-over-time &lt;em&gt;is&lt;/em&gt; the domain, and plain storage where it is not.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We have now seen &lt;em&gt;one source → many projections → drift gates&lt;/em&gt; applied to values, models, and state-over-time. The next three parts climb out of the data layer entirely and find the &lt;strong&gt;exact same shape&lt;/strong&gt; governing operations, agents, and the FE↔BE mirror. &lt;a href=&quot;http://localhost/blog/are-we-drifting-9-one-operation-three-interfaces&quot;&gt;Part 9: One Operation, Three Interfaces&lt;/a&gt; starts with operations: one handler, defined once, exposed as REST, MCP, and CLI at the same time.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 7: Models at Boundaries</title><link>http://localhost/blog/are-we-drifting-7-models-at-boundaries/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-7-models-at-boundaries/</guid><description>One overloaded type smeared across storage, API, and UI is the most expensive coupling there is — every layer drifts into every other. Separate models at boundaries cost a little duplication and buy back the freedom to change one layer without breaking the rest.

</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-7-models-at-boundaries&quot;&gt;Are We Drifting? — Part 7: Models at Boundaries&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;The vocabularies arc was about closed &lt;em&gt;values&lt;/em&gt;. This part is about closed &lt;em&gt;models&lt;/em&gt; — and the most expensive drift of all, the kind where two whole layers fuse into one and can no longer move independently.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is a tempting shortcut in every backend: define one &lt;code dir=&quot;auto&quot;&gt;Project&lt;/code&gt; (or &lt;code dir=&quot;auto&quot;&gt;Workout&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;User&lt;/code&gt;) struct, and use it everywhere. The database reads and writes it. The API serializes it. The domain logic mutates it. The event payload is it. The frontend types mirror it.&lt;/p&gt;
&lt;p&gt;It feels DRY. It is in fact the tightest coupling you can build.&lt;/p&gt;
&lt;p&gt;Because now a database column rename is an API break. A field the frontend needs forces a storage migration. A JSON tag bleeds into your business logic. An internal field you never meant to expose ships to clients because it happened to be on the struct. The layers cannot drift &lt;em&gt;apart&lt;/em&gt;, which sounds good — but only because they have drifted &lt;em&gt;together&lt;/em&gt; into a single thing that must change all at once, forever.&lt;/p&gt;
&lt;p&gt;So the question for models is the inverse of the one for values. For values we ask “do the copies still agree?” For models we ask: &lt;strong&gt;can each layer change without dragging the others with it?&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-principle-separate-models-at-important-boundaries&quot;&gt;The principle: separate models at important boundaries&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The architecture philosophy we build against is blunt about this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not use one model everywhere. Use separate models at important boundaries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The boundaries, and what each model is &lt;em&gt;for&lt;/em&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DB rows         store data        (storage shape: nullable columns, FKs, indexes)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Domain models   enforce rules     (a project must have an owner; a name can&apos;t be empty)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Commands        express intent    (authenticated, authorized — the server sets ActorID)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Events          record facts      (something that already happened; see Part 8)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Read models     serve screens     (joins, denormalized, query-optimized)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;API/DTO models  serve clients     (compatibility, security, pagination — promises)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;External models isolate vendors   (a third-party shape stops at the adapter)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The point is not ceremony. It is that each of these is shaped by a &lt;em&gt;different force&lt;/em&gt;. A DB row is shaped by storage and indexing. An API model is shaped by client compatibility and security — and once published, it is expensive to change. A domain model is shaped by business rules and should not know what a JSON field name or a SQL null wrapper is. Smashing them together means every one of those forces pulls on every layer.&lt;/p&gt;
&lt;p&gt;The translation between them is not waste; it is the seam that lets one side change without the other noticing:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;CreateProjectRequest   (what the client sends)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; CreateProjectCommand  (trusted intent: server adds ActorID)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; domain.Project        (rules enforced)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; db.ProjectRow         (storage shape)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; ProjectCreatedEvent   (the fact)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; ProjectView           (what the screen needs)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;One discipline worth singling out: where a domain model wraps a generated storage row, it should do so by &lt;strong&gt;private composition, not public embedding&lt;/strong&gt;. Embedding the DB struct re-exposes every column and lets anything mutate state outside your rules; holding it as a private field and exposing behavior (&lt;code dir=&quot;auto&quot;&gt;Rename&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Archive&lt;/code&gt;) keeps the invariants where they belong.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-far-liftmere-takes-this&quot;&gt;How far Liftmere takes this&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We apply the principle hardest at the two boundaries that hurt most when they fuse: storage and the wire.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Storage models are generated and private.&lt;/strong&gt; Our SQL is hand-authored; &lt;code dir=&quot;auto&quot;&gt;sqlc&lt;/code&gt; generates the Go row structs and query methods from it, and those generated models live behind the repository layer. A &lt;code dir=&quot;auto&quot;&gt;check-sqlc&lt;/code&gt; gate fails the build if the committed generated code drifts from the SQL — the same &lt;em&gt;one source → projection → gate&lt;/em&gt; shape as the vocabularies, applied to the database access layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The repo interface seam keeps storage from leaking up.&lt;/strong&gt; A handler depends on a repository &lt;em&gt;interface&lt;/em&gt;, not on the concrete Postgres type — so &lt;code dir=&quot;auto&quot;&gt;pgx&lt;/code&gt; and the row structs stay below the seam, and the handler can be tested against a fake. This is a governed sub-system on the backend (the “Repo Interface Seam”): the boundary is a structural rule, not a habit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The wire model is the proto contract.&lt;/strong&gt; API messages are defined in protobuf and generated into Go and TypeScript. The client never sees a database row; it sees a generated message shaped for clients. That seam is what &lt;a href=&quot;http://localhost/blog/are-we-drifting-5-the-error-manifest&quot;&gt;Part 5: The Error Manifest&lt;/a&gt; and the later contract parts lean on.&lt;/p&gt;
&lt;p&gt;The full six-model taxonomy above is the &lt;em&gt;philosophy&lt;/em&gt;. Our Go API is deliberately leaner: handlers are thin and do their validation-and-mapping in one pass rather than through a separate pure &lt;strong&gt;domain&lt;/strong&gt; layer or an explicit &lt;strong&gt;command&lt;/strong&gt; type — there is no &lt;code dir=&quot;auto&quot;&gt;flow.go&lt;/code&gt; equivalent on the backend. The seams we enforce mechanically are the storage seam (sqlc + repo interface) and the wire seam (proto). The command/domain/read-model distinctions are applied by judgment where a domain has real invariants, and skipped where it is just CRUD. Events — the “facts” boundary — are real, but they live in a dedicated plane that gets its own part next.&lt;/p&gt;
&lt;p&gt;That selectiveness is itself a rule from the philosophy:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If a model is mostly CRUD, wrapping the generated DB model is fine. If it has real invariants, hide the DB model behind methods. If it becomes central to the business, graduate it to a hand-written domain type.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You do not pay for boundaries you do not need. You pay for the two that always earn it — storage and the wire — and you enforce &lt;em&gt;those&lt;/em&gt; with a gate.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-mechanically-enforced-actually-means-here&quot;&gt;What “mechanically enforced” actually means here&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;“Enforced at the storage and wire seams” is not a policy. It is five named gates, each catching a specific way those seams can break.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;make check-sqlc&lt;/code&gt; regenerates the &lt;code dir=&quot;auto&quot;&gt;sqlc&lt;/code&gt; output and fails if the committed files differ from what the hand-authored SQL would produce — the same one-source-→-projection-→-gate shape as the vocabulary layer, applied to database access.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;make check-sql-scope&lt;/code&gt; catches the silent corruption that leaks data across tenant boundaries: a query that touches &lt;code dir=&quot;auto&quot;&gt;workouts&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;programs&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;media_assets&lt;/code&gt; without a &lt;code dir=&quot;auto&quot;&gt;user_id&lt;/code&gt; filter fails the build before the PR is reviewed.&lt;/p&gt;
&lt;p&gt;Both run inside &lt;code dir=&quot;auto&quot;&gt;lint-be&lt;/code&gt;, so neither is a suggestion you remember on a good day.&lt;/p&gt;
&lt;p&gt;The interface seam is enforced by two &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; rules in &lt;code dir=&quot;auto&quot;&gt;apps/api/.golangci.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;handler-no-db-driver&lt;/code&gt; refuses any import of &lt;code dir=&quot;auto&quot;&gt;github.com/jackc/pgx/v5&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;pgxpool&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;**/internal/*/handler.go&lt;/code&gt; — if the handler can’t import the DB driver, it is structurally forced to go through the interface.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;repo-no-transport&lt;/code&gt; refuses &lt;code dir=&quot;auto&quot;&gt;connectrpc.com/connect&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;net/http&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;**/internal/*/repo.go&lt;/code&gt; — the repo returns domain errors; only the handler maps them to Connect codes.&lt;/p&gt;
&lt;p&gt;Together those two rules mean the only dependency between the layers is the &lt;code dir=&quot;auto&quot;&gt;reader&lt;/code&gt; interface declared by the handler in its own package.&lt;/p&gt;
&lt;p&gt;The last gate is a compile assertion in every handler file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; workoutv1connect.WorkoutServiceHandler &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;Service)(&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the concrete &lt;code dir=&quot;auto&quot;&gt;*Service&lt;/code&gt; stops satisfying the generated handler interface — because a proto message changed, or a method signature drifted — the build refuses to link.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift scenario&lt;/th&gt;&lt;th&gt;Named gate&lt;/th&gt;&lt;th&gt;Where it runs&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Generated DB row structs diverge from hand-authored SQL&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-sqlc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;lint-be&lt;/code&gt; → CI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A query touches &lt;code dir=&quot;auto&quot;&gt;workouts&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;programs&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;media_assets&lt;/code&gt; without a &lt;code dir=&quot;auto&quot;&gt;user_id&lt;/code&gt; filter&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-sql-scope&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;lint-be&lt;/code&gt; → CI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;handler.go&lt;/code&gt; imports &lt;code dir=&quot;auto&quot;&gt;pgx&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;pgxpool&lt;/code&gt; directly&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;depguard: handler-no-db-driver&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;golangci-lint&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;repo.go&lt;/code&gt; imports &lt;code dir=&quot;auto&quot;&gt;connectrpc.com/connect&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;net/http&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;depguard: repo-no-transport&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;golangci-lint&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;*Service&lt;/code&gt; drifts from the generated handler interface&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ workoutv1connect.WorkoutServiceHandler = (*Service)(nil)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;compile time&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;One honest residual: &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; catches the package-import half of the seam, but it cannot verify that the &lt;code dir=&quot;auto&quot;&gt;reader&lt;/code&gt; field in &lt;code dir=&quot;auto&quot;&gt;Service&lt;/code&gt; is declared as the &lt;em&gt;interface&lt;/em&gt; type rather than a &lt;code dir=&quot;auto&quot;&gt;*Repo&lt;/code&gt; pointer.&lt;/p&gt;
&lt;p&gt;That residual is a named review note — when adding a new domain, the reviewer confirms &lt;code dir=&quot;auto&quot;&gt;Service.reader&lt;/code&gt; (or its equivalent) is the interface, not the concrete struct pointer.&lt;/p&gt;
&lt;p&gt;There is also one live exception: &lt;code dir=&quot;auto&quot;&gt;internal/programs&lt;/code&gt; is excluded from all linting until its Connect-RPC rewrite lands (&lt;code dir=&quot;auto&quot;&gt;TODO(programs-rpc)&lt;/code&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The gate is not there because we trust the process. It is there because we learned — with real incidents — that each of these seams breaks silently and expensively when it isn’t mechanically held.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The coherence check this removes is the slow, structural one: &lt;em&gt;if I change this storage detail, what else breaks?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With the seams in place, the answer is bounded. Reshape a table and regenerate — the repo interface absorbs it, and nothing above the seam recompiles against &lt;code dir=&quot;auto&quot;&gt;pgx&lt;/code&gt;. Add a client-facing field to a proto message — storage does not move. The layers are free to drift apart &lt;em&gt;deliberately&lt;/em&gt;, which is the only kind of freedom that lets you move fast in one place without auditing all the others.&lt;/p&gt;
&lt;p&gt;It is the same trade as everywhere in this series: a little declared duplication at the boundary, in exchange for never hand-tracing a change across layers that were never supposed to be the same thing.&lt;/p&gt;
&lt;p&gt;And the rails that buy this freedom are cheap to lay now: wiring the repo-interface seam or adding a &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; rule is an afternoon, not a quarter — the cost of the boundary has collapsed, while the cost of the coupling it prevents has not.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One of those boundaries — &lt;strong&gt;events&lt;/strong&gt;, the record of facts that already happened — turns out to be the most powerful anti-drift tool in the stack. &lt;a href=&quot;http://localhost/blog/are-we-drifting-8-projected-truth&quot;&gt;Part 8: Projected Truth&lt;/a&gt; is about &lt;strong&gt;projected truth&lt;/strong&gt;: an append-only event log as the one source, read models as disposable projections, and the ability to rebuild any view from the log when you suspect it has drifted.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 6: Config and Metrics</title><link>http://localhost/blog/are-we-drifting-6-config-and-metrics/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-6-config-and-metrics/</guid><description>A service&apos;s edges drift as quietly as its data — an env var read three ways, a metric name typed twice. Config and metrics are tagged vocabularies too, and declaring them once removes a whole class of 2am surprises.

</description><pubDate>Sun, 07 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-6-config-and-metrics&quot;&gt;Are We Drifting? — Part 6: Config and Metrics&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;The vocabularies in &lt;a href=&quot;http://localhost/blog/are-we-drifting-4-enums&quot;&gt;Part 4: Enums as Shared Vocabulary&lt;/a&gt; and &lt;a href=&quot;http://localhost/blog/are-we-drifting-5-the-error-manifest&quot;&gt;Part 5: The Error Manifest&lt;/a&gt; described a service’s &lt;em&gt;data&lt;/em&gt;. This part covers its &lt;em&gt;edges&lt;/em&gt; — the environment it trusts on the way in, and the signals it emits on the way out. Both drift. Both are the same atom.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Config drift is the classic one. An environment variable is read with &lt;code dir=&quot;auto&quot;&gt;os.Getenv&lt;/code&gt; in three different files, with three slightly different defaults, and one of them forgets the variable is required. The &lt;code dir=&quot;auto&quot;&gt;.env.example&lt;/code&gt; that is supposed to document what the service needs was last updated two features ago. The service boots fine in dev and falls over in staging because a variable nobody remembered is missing — and the failure is a nil deref deep in a request, not a clear “you forgot &lt;code dir=&quot;auto&quot;&gt;DATABASE_URL&lt;/code&gt;.”&lt;/p&gt;
&lt;p&gt;Metrics drift the same way. A counter is incremented with a string literal &lt;code dir=&quot;auto&quot;&gt;&quot;media.upload.intent_created&quot;&lt;/code&gt;, and the dashboard queries &lt;code dir=&quot;auto&quot;&gt;media.uploads.intent_created&lt;/code&gt; — one word apart, silently graphing nothing. A label gets a new value that no one added to the alert.&lt;/p&gt;
&lt;p&gt;Both are closed sets that cross a boundary — the process boundary for config, the telemetry boundary for metrics. Both want to be vocabularies.&lt;/p&gt;
&lt;p&gt;The same three-tier wall that governs the enums and the error manifest governs these edges too: a lint-error for the things that happen daily, a compile assertion for the seam that must never rot, and a CI coverage script for the inventory that drifts on its own schedule.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;config-as-a-vocabulary&quot;&gt;Config as a vocabulary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A config entry is the familiar atom: a &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt; (how Go refers to it), a &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; (the environment variable), a &lt;code dir=&quot;auto&quot;&gt;label&lt;/code&gt;, and metadata describing its type, default, and validation. Here are a few real entries from our API’s config manifest:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Port&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;PORT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;HTTP listen port&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;8080&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;DatabaseURL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;PostgreSQL connection URL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;S3SecretAccessKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;S3_SECRET_ACCESS_KEY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;S3 secret access key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;secret&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;s3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The interesting part is that &lt;strong&gt;validation is itself declarative.&lt;/strong&gt; The manifest does not just list variables; it states the rules between them, in a small vocabulary of its own:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;SupabaseJWKSURL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;SUPABASE_JWKS_URL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;one_of_group&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;supabase_jwt&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# exactly one of this group must be set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;required_when&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;AppEnv&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;staging&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;]      &lt;/span&gt;&lt;span&gt;# …but only in these environments&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;S3PublicEndpoint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;S3_PUBLIC_ENDPOINT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;must_match_or_empty_when&lt;/span&gt;&lt;span&gt;:            &lt;/span&gt;&lt;span&gt;# must equal S3Endpoint, or be empty,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;S3Endpoint&lt;/span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;# when running in staging/production&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;when&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;AppEnv&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;staging&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;required&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;required_when&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;one_of_group&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;must_match_or_empty_when&lt;/code&gt; — the relationships that usually live in a paragraph of onboarding docs (or in nobody’s head) are declared facts. From this one manifest, buildmere generates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a typed Go &lt;code dir=&quot;auto&quot;&gt;Config&lt;/code&gt; struct, so config access is &lt;code dir=&quot;auto&quot;&gt;cfg.DatabaseURL&lt;/code&gt;, never a stray &lt;code dir=&quot;auto&quot;&gt;os.Getenv&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a &lt;code dir=&quot;auto&quot;&gt;Load()&lt;/code&gt; that reads the environment and a &lt;code dir=&quot;auto&quot;&gt;Validate()&lt;/code&gt; that enforces every rule above;&lt;/li&gt;
&lt;li&gt;a &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; example file, so the documented environment cannot drift from the required one;&lt;/li&gt;
&lt;li&gt;an &lt;code dir=&quot;auto&quot;&gt;env-check&lt;/code&gt; command that validates a real &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; against the manifest.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last pair is the anti-drift hinge. The example and the validator come from the same source as the struct, so “what the service needs,” “what the example shows,” and “what the startup check enforces” are guaranteed to be the same list. A missing required variable fails at boot with a clear message — or fails &lt;code dir=&quot;auto&quot;&gt;env-check&lt;/code&gt; in CI before it ever boots.&lt;/p&gt;
&lt;p&gt;The CI tier closes that gap explicitly: &lt;code dir=&quot;auto&quot;&gt;env-check&lt;/code&gt; runs the config manifest’s declared requirements against a real &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; and fails the build when the two lists disagree — a variable the service stopped reading still documented in the example, or a newly required variable not yet added.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;metrics-as-a-vocabulary&quot;&gt;Metrics as a vocabulary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A metric entry is the same atom with instrument metadata. Here is the real media-metrics manifest:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;metric&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;..&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;MediaMetrics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UploadIntentCreated&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media.upload.intent_created&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload intents created&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;instrument&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;content_type&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;accepted&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;rejected&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;TranscodingDuration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media.transcoding.duration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Transcoding job duration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;instrument&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;histogram&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;unit&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;success&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;failure&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ActiveUploads&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media.uploads.active&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;In-progress uploads&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;instrument&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;updown_counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Notice the nested vocabulary: a label’s &lt;code dir=&quot;auto&quot;&gt;values: [accepted, rejected]&lt;/code&gt; is itself a closed set, declared inline. The metric name (&lt;code dir=&quot;auto&quot;&gt;media.upload.intent_created&lt;/code&gt;) is the &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt;; it is written exactly once, here.&lt;/p&gt;
&lt;p&gt;The generated Go is, again, &lt;strong&gt;zero-import&lt;/strong&gt; — it depends only on &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt;, and a &lt;code dir=&quot;auto&quot;&gt;Factory&lt;/code&gt; interface the project implements. This is verbatim:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Code generated by buildmere; DO NOT EDIT.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; media&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Factory builds instruments by name. Implementations live in the consuming&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// project&apos;s metrics kit; the generated code never imports it.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Factory &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Counter&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int64&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;any)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Histogram&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;unit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float64&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;any)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;UpDownCounter&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;desc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int64&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;any)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MediaMetrics&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;mediaMetrics{}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Register builds every instrument from f. Call once after the metrics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// backend is initialized.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;mediaMetrics) &lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt; Factory) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;uploadIntentCreated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Counter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;media.upload.intent_created&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Upload intents created&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transcodingDuration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Histogram&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;media.transcoding.duration&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Transcoding job duration&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;activeUploads&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UpDownCounter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;media.uploads.active&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;In-progress uploads&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// RecordUploadIntentCreated records Upload intents created.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Labels: outcome (accepted | rejected)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;m &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;mediaMetrics) &lt;/span&gt;&lt;span&gt;RecordUploadIntentCreated&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context.Context, &lt;/span&gt;&lt;span&gt;contentType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;uploadIntentCreated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;uploadIntentCreated&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content_type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;contentType&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Two things matter here.&lt;/p&gt;
&lt;p&gt;First, the call site is &lt;strong&gt;typed&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;RecordUploadIntentCreated(ctx, contentType, outcome)&lt;/code&gt;. You cannot fat-finger the metric name — it is baked into the generated method — and you cannot forget a label, because the labels are parameters. The string &lt;code dir=&quot;auto&quot;&gt;&quot;media.upload.intent_created&quot;&lt;/code&gt; exists in exactly one place in the whole codebase.&lt;/p&gt;
&lt;p&gt;Second, the generated code imports no OpenTelemetry. It defines a &lt;code dir=&quot;auto&quot;&gt;Factory&lt;/code&gt; &lt;em&gt;interface&lt;/em&gt; and depends on that. The project’s own metrics kit implements the interface (&lt;code dir=&quot;auto&quot;&gt;metricskit.BuildmereFactory&lt;/code&gt;) — the ~60-line adapter from &lt;a href=&quot;http://localhost/blog/are-we-drifting-3-buildmere&quot;&gt;Part 3: buildmere, a Codegen Kernel&lt;/a&gt; — and buildmere ships a compile-time assertion that the adapter satisfies the generated &lt;code dir=&quot;auto&quot;&gt;Factory&lt;/code&gt;. The instrument set is portable; the binding to OTel is the project’s, and the compiler checks the seam.&lt;/p&gt;
&lt;p&gt;That seam is held by a zero-cost Go idiom: &lt;code dir=&quot;auto&quot;&gt;var _ Factory = (*metricskit.BuildmereFactory)(nil)&lt;/code&gt;. That line compiles to nothing and refuses to compile at all if the adapter ever falls out of sync with the generated interface — no test to write, no CI script to wire, just a fact the compiler checks on every build.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;no-raw-values-at-the-edge&quot;&gt;No raw values at the edge&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The manifests govern the names and shapes that cross the boundary. The remaining gate governs the &lt;em&gt;values&lt;/em&gt; — the rule that nothing structured gets flattened into a raw string on the way out. That is what &lt;code dir=&quot;auto&quot;&gt;sloglint&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;apps/api/.golangci.yml&lt;/code&gt; enforces, across three properties, each added because a specific class of log corruption had already happened or was structurally guaranteed the moment an agent writes a handler without knowing the rule.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;static-msg&lt;/code&gt; fires when a developer writes &lt;code dir=&quot;auto&quot;&gt;slog.Error(&quot;query failed for user &quot; + userID)&lt;/code&gt; — the interpolation swallows the structure and makes the field invisible to a log query.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;no-mixed-args&lt;/code&gt; fires when the call mixes positional and key-value args: &lt;code dir=&quot;auto&quot;&gt;slog.Error(&quot;query failed&quot;, err, &quot;athlete_id&quot;, id)&lt;/code&gt; looks reasonable at a glance, but the error is a positional arg, not a keyed one, and &lt;code dir=&quot;auto&quot;&gt;sloglint&lt;/code&gt; rejects it.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;context: scope&lt;/code&gt; is the one with the longest tail — it requires &lt;code dir=&quot;auto&quot;&gt;slog.ErrorContext&lt;/code&gt; (the &lt;code dir=&quot;auto&quot;&gt;*Context&lt;/code&gt; variant) any time a &lt;code dir=&quot;auto&quot;&gt;ctx context.Context&lt;/code&gt; is already in scope, because trace IDs travel through context and a plain &lt;code dir=&quot;auto&quot;&gt;slog.Error&lt;/code&gt; in a handler throws that thread away.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Three lint rules. Three specific call-site shapes. Each one fires before the PR lands.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The natural extension is an &lt;code dir=&quot;auto&quot;&gt;otelslog&lt;/code&gt; bridge — feeding those structured records straight into spans when an OTLP endpoint is configured. The &lt;code dir=&quot;auto&quot;&gt;slog&lt;/code&gt; surface is the current one, and the bridge is where this goes next.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift scenario&lt;/th&gt;&lt;th&gt;Named gate&lt;/th&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;.env.example&lt;/code&gt; drifts from what the manifest declares required&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;env-check&lt;/code&gt; against the manifest&lt;/td&gt;&lt;td&gt;CI · coverage-script&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;New &lt;code dir=&quot;auto&quot;&gt;metricskit&lt;/code&gt; adapter does not satisfy the generated &lt;code dir=&quot;auto&quot;&gt;Factory&lt;/code&gt; interface&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ Factory = (*metricskit.BuildmereFactory)(nil)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;compile-enforced&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;slog.Error(&quot;query failed for user &quot; + userID)&lt;/code&gt; — interpolated message swallows structure&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;sloglint: static-msg&lt;/code&gt; · &lt;code dir=&quot;auto&quot;&gt;apps/api/.golangci.yml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;slog.Error(&quot;query failed&quot;, err, &quot;athlete_id&quot;, id)&lt;/code&gt; — positional arg mixed with key-value pair&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;sloglint: no-mixed-args&lt;/code&gt; · &lt;code dir=&quot;auto&quot;&gt;apps/api/.golangci.yml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;slog.Error(...)&lt;/code&gt; called while &lt;code dir=&quot;auto&quot;&gt;ctx context.Context&lt;/code&gt; is in scope&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;sloglint: context: scope&lt;/code&gt; · &lt;code dir=&quot;auto&quot;&gt;apps/api/.golangci.yml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;lint-error&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;One exclusion worth naming: &lt;code dir=&quot;auto&quot;&gt;internal/programs&lt;/code&gt; is excluded from all linters pending its Connect-RPC rewrite — &lt;code dir=&quot;auto&quot;&gt;TODO(programs-rpc)&lt;/code&gt; — so the three &lt;code dir=&quot;auto&quot;&gt;sloglint&lt;/code&gt; rules do not fire there today.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For config, the check this removes is the onboarding-and-2am one: &lt;em&gt;which environment variables does this service actually need, and which are required where?&lt;/em&gt; That is now the manifest, enforced at boot and in CI. Nobody greps for &lt;code dir=&quot;auto&quot;&gt;os.Getenv&lt;/code&gt; to reconstruct the answer.&lt;/p&gt;
&lt;p&gt;For metrics, it removes the silent-dashboard check: &lt;em&gt;does the name I emit match the name I graph, and did I pass the right labels?&lt;/em&gt; The name is written once and the call is typed, so the emit side cannot drift from the declared signal.&lt;/p&gt;
&lt;p&gt;In both cases the edge of the service is described in one declarative place, and the generated code plus the gate make the description binding.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;That closes the vocabularies arc — the &lt;em&gt;one source → many projections → drift gates&lt;/em&gt; shape applied to data, failures, environment, and telemetry. &lt;a href=&quot;http://localhost/blog/are-we-drifting-7-models-at-boundaries&quot;&gt;Part 7: Models at Boundaries&lt;/a&gt; zooms out from closed value sets to whole &lt;em&gt;models&lt;/em&gt;, and the discipline that keeps a “project” or a “workout” from becoming one overloaded struct smeared across the database, the API, and the frontend.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 5: The Error Manifest</title><link>http://localhost/blog/are-we-drifting-5-the-error-manifest/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-5-the-error-manifest/</guid><description>An error is the clearest place to see FE↔BE stitching, because a failure has to mean the same thing on both ends. We follow one real error manifest from declaration to handler to wire, and show where the generated coupling goes next.

</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-5-the-error-manifest&quot;&gt;Are We Drifting? — Part 5: The Error Manifest&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-4-enums&quot;&gt;Part 4: Enums as Shared Vocabulary&lt;/a&gt; followed the base vocabulary — an enum — to its projections. An &lt;strong&gt;error&lt;/strong&gt; is that same atom plus the metadata a failure needs. It is also the clearest cross-stack example in the series, because an error only does its job if it means the &lt;em&gt;same thing&lt;/em&gt; on the backend that raised it and the frontend that handles it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A failure is a contract between two sides that never share a process.&lt;/p&gt;
&lt;p&gt;The backend decides something went wrong. The frontend has to decide what to show, whether to offer a retry, whether to send the user somewhere. Between them is a wire, and across that wire the failure has to survive as something more useful than a 500 and a string.&lt;/p&gt;
&lt;p&gt;Drift here is especially nasty because it is invisible until the unhappy path runs in production. The backend starts returning a new error; the frontend has a &lt;code dir=&quot;auto&quot;&gt;switch&lt;/code&gt; that does not know about it and falls through to “Something went wrong.” Or the same conceptual failure gets a &lt;code dir=&quot;auto&quot;&gt;409&lt;/code&gt; in one handler and a &lt;code dir=&quot;auto&quot;&gt;400&lt;/code&gt; in another, and the frontend’s retry logic guesses wrong. Nobody tested it, because it is the path you hope never happens.&lt;/p&gt;
&lt;p&gt;So: is a failure declared once and understood identically on both ends? Or is it re-invented on each side and hoped into alignment?&lt;/p&gt;
&lt;p&gt;In this codebase, three rules enforce the answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A handler calls &lt;code dir=&quot;auto&quot;&gt;fmt.Errorf(&quot;asset not found&quot;)&lt;/code&gt; directly → &lt;code dir=&quot;auto&quot;&gt;check-connect-errors&lt;/code&gt; (CI script) fails the build; &lt;code dir=&quot;auto&quot;&gt;connect.NewError&lt;/code&gt; is only allowed inside the error-adapter package.&lt;/li&gt;
&lt;li&gt;A handler raises &lt;code dir=&quot;auto&quot;&gt;media.AssetOwnershipViolation().WithAssetID(id)&lt;/code&gt; → the Connect code (&lt;code dir=&quot;auto&quot;&gt;permission_denied&lt;/code&gt;), the typed fields (&lt;code dir=&quot;auto&quot;&gt;asset_id&lt;/code&gt;), and the wire string (&lt;code dir=&quot;auto&quot;&gt;asset_ownership_violation&lt;/code&gt;) all come from the manifest; no handler invents them.&lt;/li&gt;
&lt;li&gt;The same error crosses the wire as a structured Connect error → the frontend receives a typed code and typed fields, not a parsed HTTP status and a guessed English message.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third rule is the FE↔BE seam that carries today. The fuller stitch — where the frontend’s error constants are also generated from the same manifest — is where this goes next.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;one-source&quot;&gt;One source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is a real error vocabulary from the media module. Three failure modes, declared once:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;..&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;MediaErrors&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UnsupportedContentType&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;unsupported_content_type&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Unsupported content type&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;invalid_argument&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;content_type&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UploadTooLarge&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;upload_too_large&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload exceeds size limit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;invalid_argument&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;asset_id&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;size_bytes&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max_bytes&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;AssetOwnershipViolation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;asset_ownership_violation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Asset does not belong to requesting user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;permission_denied&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;asset_id&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It is the same shape as the enum from Part 4 — &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;label&lt;/code&gt; — with two pieces of failure-specific metadata per entry: a transport &lt;code dir=&quot;auto&quot;&gt;code&lt;/code&gt; (a Connect error code) and the typed &lt;code dir=&quot;auto&quot;&gt;fields&lt;/code&gt; this error carries as structured context.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-backend-projection&quot;&gt;The backend projection&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;buildmere generates a zero-import Go type per error. This is the &lt;strong&gt;actual&lt;/strong&gt; generated code for the first one — note that it imports nothing but &lt;code dir=&quot;auto&quot;&gt;fmt&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Code generated by buildmere; DO NOT EDIT.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; media&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// UnsupportedContentTypeError is the typed error for unsupported_content_type.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Wire: &quot;unsupported_content_type&quot; | Code: &quot;invalid_argument&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; UnsupportedContentTypeError &lt;/span&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;contentType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cause&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UnsupportedContentType&lt;/span&gt;&lt;span&gt;() UnsupportedContentTypeError { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; UnsupportedContentTypeError{} }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;WithContentType&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) UnsupportedContentTypeError {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;contentType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Wire&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;unsupported_content_type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Code&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;invalid_argument&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Fields&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;]any {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;]any{&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content_type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;contentType&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;unsupported content type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Unwrap&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cause&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Is&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.(&lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;Wire&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Wire&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Wire&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span&gt;UnsupportedContentTypeError) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Wrap&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cause&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A handler raises it fluently, with the typed field attached as structured context:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;media&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UploadTooLarge&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;WithAssetID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;WithSizeBytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FormatInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;WithMaxBytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FormatInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;Code()&lt;/code&gt; — &lt;code dir=&quot;auto&quot;&gt;invalid_argument&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;permission_denied&lt;/code&gt; — is the value that crosses the wire. The generated struct is deliberately inert: it knows its wire string, its transport code, and its fields, and &lt;em&gt;nothing about Connect.&lt;/em&gt; A small adapter the project owns turns a coded error into a &lt;code dir=&quot;auto&quot;&gt;connect.NewError(...)&lt;/code&gt; at the transport boundary — and in our repo a CI check (&lt;code dir=&quot;auto&quot;&gt;check-connect-errors&lt;/code&gt;) enforces that &lt;code dir=&quot;auto&quot;&gt;connect.NewError&lt;/code&gt; appears &lt;em&gt;only&lt;/em&gt; in that one adapter package, never scattered through handlers. One place translates application failures into wire failures.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-frontend-projection--and-where-this-goes&quot;&gt;The frontend projection — and where this goes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the side-by-side.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What the wire carries:&lt;/strong&gt; the frontend learns about the failure through the contract. The error crosses the wire as a structured Connect error carrying the &lt;code dir=&quot;auto&quot;&gt;code&lt;/code&gt; and the typed &lt;code dir=&quot;auto&quot;&gt;fields&lt;/code&gt; from the manifest. The generated Connect client surfaces it as a typed error, so the frontend is reasoning about &lt;code dir=&quot;auto&quot;&gt;permission_denied&lt;/code&gt; and a &lt;code dir=&quot;auto&quot;&gt;content_type&lt;/code&gt; field — &lt;em&gt;not&lt;/em&gt; parsing a 403 and a sentence of English. That is a real cross-boundary coupling: the backend declared the failure once, and the frontend receives a structured, coded shape rather than a guess.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next emitter:&lt;/strong&gt; the philosophy this manifest is built toward goes one step further — generating the &lt;em&gt;frontend’s&lt;/em&gt; error vocabulary from the same source. From one declaration you also emit:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// the frontend projection from the same manifest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;MediaError&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UnsupportedContentType: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;unsupported_content_type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UploadTooLarge: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;upload_too_large&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AssetOwnershipViolation: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;asset_ownership_violation&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;} as const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;…plus safe user-facing messages, a &lt;code dir=&quot;auto&quot;&gt;retryable&lt;/code&gt; hint, and a category so the UI can branch exhaustively on the error code instead of on an HTTP status. At that point a backend engineer adding an error and a frontend engineer handling it are reading projections of the &lt;em&gt;same row&lt;/em&gt;, and a missing case is a compile error on the frontend.&lt;/p&gt;
&lt;p&gt;buildmere’s &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; kind emits Go; the TypeScript emitter is the natural extension — the kernel is built for exactly this, since adding a language to a kind is a new generator package. The backend half ships and is gated, the wire-level coupling ships through the contract, and the generated frontend half is the next emitter.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-design-principle-worth-stealing-now&quot;&gt;A design principle worth stealing now&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One rule from the error philosophy is worth applying by hand right now: &lt;strong&gt;the application error code is the source of truth, and transport codes derive from it — never the reverse.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is tempting to let HTTP status be the canonical thing. But the mappings are not one-to-one. &lt;code dir=&quot;auto&quot;&gt;HTTP 409&lt;/code&gt; can mean “already exists” &lt;em&gt;or&lt;/em&gt; “version conflict”; &lt;code dir=&quot;auto&quot;&gt;HTTP 400&lt;/code&gt; can mean “invalid argument” &lt;em&gt;or&lt;/em&gt; “failed precondition.” If you make HTTP the source, you lose that distinction on the way in and guess it back on the way out.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An error code names the &lt;em&gt;application&lt;/em&gt; failure. HTTP, gRPC/Connect, an MCP tool response, a CLI exit, and a frontend display are all &lt;em&gt;renderings&lt;/em&gt; of it. The code owns its mappings; no transport is the universal source of truth.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Our manifest carries the Connect &lt;code dir=&quot;auto&quot;&gt;code&lt;/code&gt; directly on each entry for this reason — the failure’s meaning travels with the failure, and each edge decides how to render it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The coherence check this removes is the one that only fails in production: &lt;em&gt;does the frontend’s handling of this failure match the failure the backend actually emits?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Today that is partly structural (the code and fields cross the wire as a typed contract, not a parsed status) and partly still manual (the frontend’s message and branching are hand-written). The frontend emitter closes the gap entirely. Either way, the direction is the one this series argues: move the failure’s definition into one declared place, and let each side’s handling be generated or contract-checked rather than independently authored and hoped into agreement.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-6-config-and-metrics&quot;&gt;Part 6: Config and Metrics&lt;/a&gt; finishes the vocabularies arc with the two kinds that govern a service’s &lt;em&gt;edges&lt;/em&gt; rather than its data — &lt;strong&gt;config&lt;/strong&gt; (the environment a service trusts) and &lt;strong&gt;metrics&lt;/strong&gt; (the signals it emits) — both declared as the same atom, both gated the same way.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 4: Enums as Shared Vocabulary</title><link>http://localhost/blog/are-we-drifting-4-enums/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-4-enums/</guid><description>An enum is the simplest tagged vocabulary, so it is the clearest place to watch one source become many projections. We follow a real VideoStatus manifest from YAML to Go, and look at what keeps it from drifting as it grows.

</description><pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-4-enums-as-shared-vocabulary&quot;&gt;Are We Drifting? — Part 4: Enums as Shared Vocabulary&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-3-buildmere&quot;&gt;Part 3: buildmere, a Codegen Kernel&lt;/a&gt; introduced the kernel in the abstract. The &lt;code dir=&quot;auto&quot;&gt;enum&lt;/code&gt; kind is the simplest place to make it concrete — so let us follow one enum from its manifest all the way out to every side that has to agree about it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An enum is a closed set of named values. It is also the single most common thing to drift, because it shows up in the most places: a Go type, a TypeScript union, a dropdown, a validator, a database column, an analytics dimension, a model’s structured output.&lt;/p&gt;
&lt;p&gt;The drift is always the same story. Someone adds a value on one side. The other sides do not hear about it. A row appears in the database that the frontend cannot render; a model returns a status the backend rejects; a dashboard silently stops counting a state that was renamed.&lt;/p&gt;
&lt;p&gt;So the test for the &lt;code dir=&quot;auto&quot;&gt;enum&lt;/code&gt; kind is direct: can a value exist on one side that another side has never heard of? The answer should be &lt;em&gt;structurally no.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this codebase, here is what “structurally no” looks like in practice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A new &lt;code dir=&quot;auto&quot;&gt;VideoStatus&lt;/code&gt; value added to the YAML manifest → &lt;code dir=&quot;auto&quot;&gt;make gen-vocab&lt;/code&gt; regenerates Go, TS, Zod, SQL &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; in one pass; &lt;code dir=&quot;auto&quot;&gt;make check-vocab&lt;/code&gt; fails the PR if any committed projection is out of date.&lt;/li&gt;
&lt;li&gt;A &lt;code dir=&quot;auto&quot;&gt;VideoStatus&lt;/code&gt; string typed by hand in handler code → the Go type system rejects anything that isn’t a &lt;code dir=&quot;auto&quot;&gt;VideoStatus&lt;/code&gt; constant; no raw string survives &lt;code dir=&quot;auto&quot;&gt;tsc&lt;/code&gt; or the Go compiler.&lt;/li&gt;
&lt;li&gt;A row inserted with an unknown status → the database &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint rejects it before it lands.&lt;/li&gt;
&lt;li&gt;A backlog ticket assigned to an owner not in &lt;code dir=&quot;auto&quot;&gt;enums.mjs&lt;/code&gt; → the Zod schema on the operation input rejects the value at the REST/MCP/CLI boundary.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;one-source&quot;&gt;One source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is a real enum from our backend — the lifecycle of an uploaded video. This is the entire source of truth:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;enum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;../enums&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;VideoStatus&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Uploading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Uploading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Processing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Processing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Ready&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Ready&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;terminal&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Five values, each with a code-facing &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;, a boundary-facing &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt;, and a human &lt;code dir=&quot;auto&quot;&gt;label&lt;/code&gt;. One of them is flagged &lt;code dir=&quot;auto&quot;&gt;terminal&lt;/code&gt;. That is the whole declaration.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;many-projections&quot;&gt;Many projections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Running the generator turns that manifest into typed code. This is the &lt;strong&gt;actual&lt;/strong&gt; generated Go — not a sketch:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Code generated by buildmere; DO NOT EDIT.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; enums&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; VideoStatus &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusUploading&lt;/span&gt;&lt;span&gt;  VideoStatus &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusProcessing&lt;/span&gt;&lt;span&gt; VideoStatus &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusReady&lt;/span&gt;&lt;span&gt;      VideoStatus &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusFailed&lt;/span&gt;&lt;span&gt;     VideoStatus &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusArchived&lt;/span&gt;&lt;span&gt;   VideoStatus &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span&gt;VideoStatus) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IsValid&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VideoStatusUploading&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusProcessing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusReady&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusFailed&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusArchived&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;videoStatusLabels&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;[VideoStatus]&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusUploading&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Uploading&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusProcessing&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Processing&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusReady&lt;/span&gt;&lt;span&gt;:      &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Ready&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusFailed&lt;/span&gt;&lt;span&gt;:     &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;VideoStatusArchived&lt;/span&gt;&lt;span&gt;:   &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Archived&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;v &lt;/span&gt;&lt;span&gt;VideoStatus) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;videoStatusLabels&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;] }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AllVideoStatus&lt;/span&gt;&lt;span&gt;() []VideoStatus {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; []VideoStatus{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusUploading&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusProcessing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusReady&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusFailed&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;VideoStatusArchived&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Note the &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; split surviving into the code: &lt;code dir=&quot;auto&quot;&gt;VideoStatusReady&lt;/code&gt; (the name) is the constant; &lt;code dir=&quot;auto&quot;&gt;&quot;ready&quot;&lt;/code&gt; (the wire) is its value. Code reads &lt;code dir=&quot;auto&quot;&gt;VideoStatusReady&lt;/code&gt;; the byte on the wire is &lt;code dir=&quot;auto&quot;&gt;ready&lt;/code&gt;; the label &lt;code dir=&quot;auto&quot;&gt;&quot;Ready&quot;&lt;/code&gt; is for humans. Three concerns, one declaration, no place for them to disagree.&lt;/p&gt;
&lt;p&gt;The same manifest is what the &lt;code dir=&quot;auto&quot;&gt;enum&lt;/code&gt; kind projects to the other sides. A TypeScript union and label map:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;VideoStatus&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Uploading: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Processing: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Ready: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Failed: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Archived: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;} as const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; VideoStatus &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; VideoStatus)[&lt;/span&gt;&lt;span&gt;keyof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; VideoStatus];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;videoStatusLabels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoStatus&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uploading: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Uploading&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;processing: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Processing&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ready: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Ready&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;failed: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;archived: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Archived&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A Zod schema for runtime validation at the edge:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;VideoStatusSchema&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;enum&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And a SQL &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint so the database itself refuses an unknown value:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ALTER&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TABLE&lt;/span&gt;&lt;span&gt; videos&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ADD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONSTRAINT&lt;/span&gt;&lt;span&gt; videos_status_check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;CHECK&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IN&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The dropdown iterates &lt;code dir=&quot;auto&quot;&gt;AllVideoStatus()&lt;/code&gt; (or the TS union). The validator is &lt;code dir=&quot;auto&quot;&gt;VideoStatusSchema&lt;/code&gt;. The column is constrained. None of these is hand-maintained; all of them are the same five entries, projected.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;keeping-an-enum-honest-over-time&quot;&gt;Keeping an enum honest over time&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A closed set is only safe if changing it is disciplined. The rules are simple and they are the same ones a careful DBA already follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Values are append-only by default.&lt;/strong&gt; Adding &lt;code dir=&quot;auto&quot;&gt;Suspended&lt;/code&gt; is safe. The manifest grows; every projection regenerates; the &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint is widened by a new migration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Renaming is not a rename.&lt;/strong&gt; Changing a &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; value is a data migration in disguise — existing rows still hold the old string. You &lt;em&gt;deprecate&lt;/em&gt; the old value and &lt;em&gt;add&lt;/em&gt; the new one, then migrate, then remove.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal waits for the data.&lt;/strong&gt; A value cannot leave the vocabulary while a single row or in-flight event still references it. Mark it &lt;code dir=&quot;auto&quot;&gt;deprecated&lt;/code&gt; with a note, migrate, and only then delete.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deprecation is itself just metadata on the entry — &lt;code dir=&quot;auto&quot;&gt;deprecated: true&lt;/code&gt; with a &lt;code dir=&quot;auto&quot;&gt;deprecation_note&lt;/code&gt; — so “this value is on its way out” is a fact the generated code and docs can carry, not tribal knowledge.&lt;/p&gt;
&lt;p&gt;The database &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint is the backstop. Application code can be wrong; a migration can lag; but a column constrained from the same manifest will reject a value that was never declared. The truth has a floor.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;not-every-vocabulary-needs-a-generator&quot;&gt;Not every vocabulary needs a generator&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Codegen is the right tool when a vocabulary crosses &lt;em&gt;language&lt;/em&gt; boundaries. Some of ours do not, and they use a lighter mechanism.&lt;/p&gt;
&lt;p&gt;Our backlog’s enums — ticket statuses, areas, efforts, owners — live in a single &lt;code dir=&quot;auto&quot;&gt;enums.mjs&lt;/code&gt; module that every consumer imports directly. The frontend reaches them over REST; the CLI and the backlog services import the file. Some are static (&lt;code dir=&quot;auto&quot;&gt;STATUSES&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;AREAS&lt;/code&gt;); one, &lt;code dir=&quot;auto&quot;&gt;OWNERS&lt;/code&gt;, is &lt;em&gt;derived&lt;/em&gt; at call time by scanning the agent definitions on disk, so it can never go stale against the set of agents that actually exist.&lt;/p&gt;
&lt;p&gt;And there is a fourth consumer the others do not have: an &lt;strong&gt;agent&lt;/strong&gt;, which discovers the set at runtime rather than compiling it in. The same enums are queryable over MCP — an agent asks for the live owners or statuses (&lt;code dir=&quot;auto&quot;&gt;backlog_list_enums&lt;/code&gt;) instead of hard-coding them, and a stale agent never invents an owner that does not exist. An enum, in other words, is the smallest instance of a vocabulary that systems serialize, humans speak, and agents discover — the same closed set, reached three different ways.&lt;/p&gt;
&lt;p&gt;Different mechanism, identical principle: &lt;strong&gt;one source, many consumers, no second copy to drift.&lt;/strong&gt; Codegen is how you get there across languages; a shared module is how you get there within one; runtime discovery is how an agent gets there without compiling at all. The discipline is the same.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The check this removes is the most tedious one in code review: &lt;em&gt;you added a status — did you update the type, the dropdown, the validator, and the constraint?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That question disappears. You add an entry to the manifest, regenerate, and every side updates together. The reviewer does not audit five files for consistency, because consistency is not something a human is maintaining. And if anyone hand-edits a generated file to “just add it here,” &lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; fails the build.&lt;/p&gt;
&lt;p&gt;You can let an agent add a video state and trust the diff, because the only way to add one wrong is a way the gate rejects.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Enums are the base vocabulary. &lt;a href=&quot;http://localhost/blog/are-we-drifting-5-the-error-manifest&quot;&gt;Part 5: The Error Manifest&lt;/a&gt; adds the first layer of kind-specific metadata and lands the series’ clearest cross-stack example: the &lt;strong&gt;error manifest&lt;/strong&gt;, where one declaration stitches a backend failure to a frontend’s handling of it.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 3: buildmere, a Codegen Kernel</title><link>http://localhost/blog/are-we-drifting-3-buildmere/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-3-buildmere/</guid><description>A manifest is only a source of truth if something keeps every generated copy honest. buildmere is that something — a kernel that knows nothing about kinds, a set of plugins that do, and a check command that fails the build on the first byte of drift.

</description><pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-3-buildmere-a-codegen-kernel&quot;&gt;Are We Drifting? — Part 3: buildmere, a Codegen Kernel&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-2-the-tagged-vocabulary&quot;&gt;Part 2: The Tagged Vocabulary&lt;/a&gt; named the atom: a tagged vocabulary, declared once. This part is about the machine that turns that one declaration into typed code on every side — and, more importantly, keeps the copies from drifting after it does.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A manifest only earns the name “source of truth” if the copies generated from it cannot quietly disagree with it.&lt;/p&gt;
&lt;p&gt;A generator that you run by hand, whose output you commit and then edit “just this once,” is not a source of truth. It is a suggestion with extra steps. The generated file drifts from the manifest the moment someone touches it, and nothing notices.&lt;/p&gt;
&lt;p&gt;So the real question for any codegen system is not “can it generate?” It is “what stops the generated code from drifting away from its source?“&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;buildmere-in-one-paragraph&quot;&gt;buildmere, in one paragraph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;buildmere&lt;/code&gt; is a standalone Go module — &lt;code dir=&quot;auto&quot;&gt;github.com/buildmere/vocab&lt;/code&gt; — that does exactly one thing: declare a closed tagged vocabulary once in a YAML manifest, and generate typed code, validation, schemas, and reference files across the stack from that single source.&lt;/p&gt;
&lt;p&gt;Two rules keep it honest and portable, and they are worth stating verbatim because everything else follows from them:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1. buildmere imports nothing from its consumers.&lt;/strong&gt; It is imported by products like Liftmere; never the reverse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Generated code imports nothing project-specific&lt;/strong&gt; — only &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;fmt&lt;/code&gt; from the standard library. Binding generated code to a project’s runtime is done by ~60 lines of &lt;em&gt;adapter&lt;/em&gt; the project owns, not by the generator.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first rule means buildmere can be lifted into its own repository with a &lt;code dir=&quot;auto&quot;&gt;git mv&lt;/code&gt;. The second means every generated artifact is adoptable by &lt;em&gt;any&lt;/em&gt; project, not just ours — the generated code is inert until a small, hand-owned adapter wires it to the real metrics backend or error transport.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-kernel-that-knows-nothing-about-kinds&quot;&gt;A kernel that knows nothing about kinds&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The architecture is a kernel plus plugins. The kernel ships &lt;strong&gt;zero&lt;/strong&gt; kinds.&lt;/p&gt;
&lt;p&gt;The kernel is small and generic:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Manifest, Entry        the parsed source — a list of vocabulary entries&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Generator              interface: given a Manifest, return Artifacts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Artifact               an in-memory file: path + contents (not written yet)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Registry               maps a kind (&quot;enum&quot;, &quot;error&quot;, …) to its generators&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;KindDescriptor         how a kind declares its metadata schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Generate / Write / Check   the pipeline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;kind&lt;/strong&gt; is a plugin: a root package that knows how to parse its kind-specific metadata, plus one sub-package per target language. The kernel never hard-codes the list of kinds; the registry resolves a kind string to its generators at runtime. Adding a new output language to a kind is a new package under &lt;code dir=&quot;auto&quot;&gt;generators/&lt;/code&gt; — it touches nothing that already works.&lt;/p&gt;
&lt;p&gt;The kinds that ship today:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Kind&lt;/th&gt;&lt;th&gt;The manifest declares&lt;/th&gt;&lt;th&gt;Generators&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;enum&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a closed value set&lt;/td&gt;&lt;td&gt;Go constants, TypeScript, Zod, JSON-Schema, SQL &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;config&lt;/code&gt;&lt;/td&gt;&lt;td&gt;an env-var schema + validation&lt;/td&gt;&lt;td&gt;Go &lt;code dir=&quot;auto&quot;&gt;Config&lt;/code&gt; struct + &lt;code dir=&quot;auto&quot;&gt;Load&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;Validate&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; example&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a domain error vocabulary + fields&lt;/td&gt;&lt;td&gt;zero-import Go error structs (&lt;code dir=&quot;auto&quot;&gt;Code&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;Wire&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;Fields&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;Is&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;Wrap&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;metric&lt;/code&gt;&lt;/td&gt;&lt;td&gt;an OTel instrument set + labels&lt;/td&gt;&lt;td&gt;zero-import Go metric set + a &lt;code dir=&quot;auto&quot;&gt;Factory&lt;/code&gt; interface&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;generators-are-pure-functions&quot;&gt;Generators are pure functions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the detail that makes the drift gate trustworthy.&lt;/p&gt;
&lt;p&gt;A generator does &lt;strong&gt;not&lt;/strong&gt; write to disk. It takes a manifest and returns artifacts — path-plus-bytes values, held in memory. The framework collects them, and then does one of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Write&lt;/strong&gt; — write the artifacts to disk (&lt;code dir=&quot;auto&quot;&gt;gen&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check&lt;/strong&gt; — compare the artifacts against what is already committed, and exit non-zero if a single byte differs (&lt;code dir=&quot;auto&quot;&gt;check&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because a generator is a pure function from manifest to bytes, the same call that &lt;em&gt;writes&lt;/em&gt; your code in development is the call that &lt;em&gt;verifies&lt;/em&gt; it in CI. There is no second, separately-maintained “linter” that could itself drift from the generator. The generator is the spec.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# from packages/buildmere (GOWORK=off keeps the tool&apos;s deps out of the app graph)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GOWORK&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;off&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./cmd/buildmere&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gen&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&amp;#x3C;manifest.yaml&gt;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# write artifacts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GOWORK&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;off&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./cmd/buildmere&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;manifest.yaml&gt;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# CI drift gate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GOWORK&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;off&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./cmd/buildmere&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gen-all&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;root-dir&gt;&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# discover + generate every manifest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GOWORK&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;off&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./cmd/buildmere&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check-all&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;#x3C;root-dir&gt;&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# drift gate over a whole tree&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each manifest declares its own &lt;code dir=&quot;auto&quot;&gt;output:&lt;/code&gt; path, so discovery needs no per-manifest wiring — &lt;code dir=&quot;auto&quot;&gt;gen-all&lt;/code&gt; walks a directory, finds every buildmere manifest, and generates each to where it says it belongs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-drift-gate-is-one-make-target&quot;&gt;The drift gate is one make target&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In our repo, two &lt;code dir=&quot;auto&quot;&gt;make&lt;/code&gt; targets wrap the kernel:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;make gen-vocab     -&gt; buildmere gen-all   (regenerate everything; what you run locally)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;make check-vocab   -&gt; buildmere check-all (the CI gate; fails on any drift)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; is not optional or advisory. It sits inside the backend lint umbrella that every pull request runs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;lint-be: contracts lint-migrations check-vocab check-mocks check-sqlc \&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;check-sql-scope check-connect-errors&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you hand-edit a generated &lt;code dir=&quot;auto&quot;&gt;.gen.go&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; regenerates it in memory, sees that your edit does not match what the manifest produces, and fails the build. The only way to change generated code is to change the manifest and regenerate. That is the third leg of the governance model — structure, &lt;strong&gt;enforcement&lt;/strong&gt;, docs — and without it the first two are decoration.&lt;/p&gt;
&lt;p&gt;buildmere also ships its own internal drift guards, so the &lt;em&gt;generator itself&lt;/em&gt; cannot silently change shape: a compile-time assertion that the metric &lt;code dir=&quot;auto&quot;&gt;Factory&lt;/code&gt; matches, an error-code round-trip test, and pinned generator signatures.&lt;/p&gt;
&lt;p&gt;The same invariant — generated code cannot silently disagree with its source — appears twice in the backend, enforced two different ways, and the contrast is the interesting part.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;buildmere&lt;/code&gt; outputs are &lt;strong&gt;committed to the repo&lt;/strong&gt;, which is why &lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; can catch them: it regenerates the files in memory and byte-compares against what is committed — any hand-edit is visible as a diff and fails the build.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;buf generate&lt;/code&gt; outputs are &lt;strong&gt;gitignored&lt;/strong&gt;, so they cannot drift because they are never stored in the first place — &lt;code dir=&quot;auto&quot;&gt;gen/go/&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;gen/ts/&lt;/code&gt; are produced fresh on every &lt;code dir=&quot;auto&quot;&gt;make gen-be&lt;/code&gt;, and any hand-edit to them simply disappears the next time the target runs.&lt;/p&gt;
&lt;p&gt;Two different mechanisms. The same refusal.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Generated code cannot silently disagree with its source. In this backend, that invariant is enforced twice over — once by a check that diffs what is committed, once by a store strategy that makes committing impossible.&lt;/p&gt;
&lt;/blockquote&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Drift scenario&lt;/th&gt;&lt;th&gt;Gate&lt;/th&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Hand-edit a committed &lt;code dir=&quot;auto&quot;&gt;.gen.go&lt;/code&gt; vocab file&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; inside &lt;code dir=&quot;auto&quot;&gt;lint-be&lt;/code&gt;: regenerates in memory, byte-compares, exits non-zero&lt;/td&gt;&lt;td&gt;coverage-script (tier 3)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Add an RPC to the proto, skip the handler method&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ workoutv1connect.WorkoutServiceHandler = (*Service)(nil)&lt;/code&gt; — build fails&lt;/td&gt;&lt;td&gt;compile-enforced (tier 1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Delete a proto method, handler still references the old interface&lt;/td&gt;&lt;td&gt;same compile assertion, same instant failure&lt;/td&gt;&lt;td&gt;compile-enforced (tier 1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hand-edit anything under &lt;code dir=&quot;auto&quot;&gt;gen/go/&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;gen/ts/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make gen-be&lt;/code&gt; overwrites it; &lt;code dir=&quot;auto&quot;&gt;gen/&lt;/code&gt; is gitignored so the edit was never committed&lt;/td&gt;&lt;td&gt;structural (gitignore)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Neither of these gates requires developer discipline to fire on a PR: &lt;code dir=&quot;auto&quot;&gt;make lint-be&lt;/code&gt; lists &lt;code dir=&quot;auto&quot;&gt;contracts&lt;/code&gt; as a prerequisite, so &lt;code dir=&quot;auto&quot;&gt;buf generate&lt;/code&gt; runs before the linters, and the compile assertion runs before that lint step even starts.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Adding a new closed set used to mean a decision and a chore: where do the constants live, who writes the validator, who remembers to update the SQL constraint, who keeps the frontend list in sync.&lt;/p&gt;
&lt;p&gt;With a kernel in place, a new vocabulary is a new manifest plus a &lt;code dir=&quot;auto&quot;&gt;make gen-vocab&lt;/code&gt;. The generator for that kind already exists. Every projection appears at once, correct by construction, and &lt;code dir=&quot;auto&quot;&gt;check-vocab&lt;/code&gt; guarantees it stays that way.&lt;/p&gt;
&lt;p&gt;And because generators are pure functions tested as pure functions, &lt;em&gt;extending the machine&lt;/em&gt; is itself cheap — a new language emitter is a small package with golden-file tests, not a research project. This is the economic inversion from &lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt; made concrete: the rails are cheap to lay, cheap to extend, and they remove a standing coherence tax.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The kernel is abstract on purpose. &lt;a href=&quot;http://localhost/blog/are-we-drifting-4-enums&quot;&gt;Part 4: Enums as Shared Vocabulary&lt;/a&gt; makes it concrete with the simplest kind — &lt;code dir=&quot;auto&quot;&gt;enum&lt;/code&gt; — and follows one manifest all the way out to Go, TypeScript, Zod, JSON Schema, and a SQL &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint.&lt;/p&gt;
&lt;p&gt;Where this goes is more reach on the same kernel: additional kinds like &lt;code dir=&quot;auto&quot;&gt;permission&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;route&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;event&lt;/code&gt;, more language emitters per kind (&lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; → TypeScript is the obvious next one), a typed &lt;code dir=&quot;auto&quot;&gt;Kind&lt;/code&gt; discriminator, and lifting buildmere into its own open-source repository with a &lt;code dir=&quot;auto&quot;&gt;git mv&lt;/code&gt;. The plugin model was built for exactly these extensions.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 2: The Tagged Vocabulary</title><link>http://localhost/blog/are-we-drifting-2-the-tagged-vocabulary/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-2-the-tagged-vocabulary/</guid><description>Before any code generator, there is one idea: a closed named set, declared once, with a stable name and a separate wire form. Get that atom right and enums, errors, config, and metrics all turn out to be the same thing.

</description><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-2-the-tagged-vocabulary&quot;&gt;Are We Drifting? — Part 2: The Tagged Vocabulary&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem&quot;&gt;Part 1: The Drift Problem&lt;/a&gt; ended on a shape: &lt;em&gt;one source → many &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Projection&quot; data-term-general=&quot;An artifact generated from a single source — a view of the truth, not the truth itself. A projection can always be regenerated from its source; if it cannot, it is a copy, not a projection. Copies drift; projections are kept honest by a drift gate.&quot; data-term-liftmere=&quot;Generated .gen.go files (buildmere), features.generated.ts (generate-features), Postgres read-model tables (event log replay). Each has a paired drift gate.&quot; data-term-href=&quot;/reference/glossary#projection&quot;&gt;projections&lt;/button&gt; → &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Drift gate&quot; data-term-general=&quot;A check that runs in CI and fails the build when a committed projection diverges from what its source would generate. The load-bearing leg of the governance model — without it, &amp;#x22;generated&amp;#x22; is just a suggestion.&quot; data-term-liftmere=&quot;make check-vocab (buildmere), generate-features --check (feature registry), make check-sqlc, make check-mocks, make agents-check. All exit non-zero on any byte of drift.&quot; data-term-href=&quot;/reference/glossary#drift-gate&quot;&gt;drift gates&lt;/button&gt;&lt;/em&gt;. This part is about the &lt;strong&gt;source&lt;/strong&gt; — specifically, the smallest, most boring unit that the entire pattern is built from.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;are-we-drifting-here&quot;&gt;Are we drifting here?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Start with the most innocent thing in any codebase: a closed set of named values.&lt;/p&gt;
&lt;p&gt;A video is &lt;code dir=&quot;auto&quot;&gt;uploading&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;processing&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ready&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;failed&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;archived&lt;/code&gt;. A ticket is &lt;code dir=&quot;auto&quot;&gt;proposed&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;accepted&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;in_progress&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;shipped&lt;/code&gt;. An upload was &lt;code dir=&quot;auto&quot;&gt;accepted&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;rejected&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These look harmless. They are strings. Everyone “knows” them.&lt;/p&gt;
&lt;p&gt;That is exactly why they drift.&lt;/p&gt;
&lt;p&gt;The backend writes &lt;code dir=&quot;auto&quot;&gt;&quot;ready&quot;&lt;/code&gt;. The frontend dropdown lists &lt;code dir=&quot;auto&quot;&gt;&quot;Ready&quot;&lt;/code&gt;. The analytics event sends &lt;code dir=&quot;auto&quot;&gt;&quot;READY&quot;&lt;/code&gt;. A migration’s &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint allows &lt;code dir=&quot;auto&quot;&gt;&apos;ready&apos;&lt;/code&gt; but a later refactor adds &lt;code dir=&quot;auto&quot;&gt;&apos;done&apos;&lt;/code&gt; on the API side and nobody updates the constraint. Three months later a row exists that the frontend cannot render, because the value in the database is a status the UI has never heard of.&lt;/p&gt;
&lt;p&gt;No single change was wrong. The set was simply written down in six places, and the six copies wandered.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-atom-a-tagged-vocabulary&quot;&gt;The atom: a tagged vocabulary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the move that makes the rest of the series possible.&lt;/p&gt;
&lt;p&gt;A closed set of named values is not a string convention. It is a &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Tagged vocabulary&quot; data-term-general=&quot;A closed named set where every entry has a stable identifier (how code refers to it), a serialized wire form (how it crosses a boundary), a human label, and optional typed metadata. The atom the whole manifest pattern is built from. Enums, errors, config, and metrics are all instances of this same primitive.&quot; data-term-liftmere=&quot;YAML manifests under apps/api/internal/*/vocab/. Fields: name (Go/TS constant), wire (JSON/DB/event string), label (display), metadata (kind-specific). Processed by buildmere.&quot; data-term-href=&quot;/reference/glossary#tagged-vocabulary&quot;&gt;&lt;strong&gt;tagged vocabulary&lt;/strong&gt;&lt;/button&gt;: a first-class, declarable thing with a precise shape.&lt;/p&gt;
&lt;p&gt;Every entry in a tagged vocabulary has three layers.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Layer 1 — Identity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name   the canonical identifier (how code refers to it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;wire   the serialized form (how it crosses a boundary)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;deprecated / deprecation_note&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Layer 2 — Presentation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label        the human-facing string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description  optional documentation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Layer 3 — Kind-specific metadata&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;whatever this kind of vocabulary additionally needs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The first layer is the load-bearing one, and the split inside it is the whole trick.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-name-and-wire-are-different-on-purpose&quot;&gt;Why &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; are different on purpose&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Code refers to a value by its &lt;strong&gt;name&lt;/strong&gt;. The boundary — JSON, a database column, an event payload, a model’s structured output — sees its &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Wire form&quot; data-term-general=&quot;The serialized representation of a concept at a boundary — the string that appears in JSON, a database column, an event payload, or a model&amp;#x27;s structured output. Kept separate from the code-facing name so the serialized form is a deliberate declaration, not an accident of casing or spelling.&quot; data-term-liftmere=&quot;The wire: field in a buildmere manifest entry. e.g. name: Ready, wire: ready. The Go constant is VideoStatusReady; the value on the wire is &amp;#x22;ready&amp;#x22;.&quot; data-term-href=&quot;/reference/glossary#wire-form&quot;&gt;&lt;strong&gt;wire&lt;/strong&gt; form&lt;/button&gt;.&lt;/p&gt;
&lt;p&gt;Keeping them separate means the serialized representation is a deliberate, declared decision, not an accident of how someone happened to spell a constant.&lt;/p&gt;
&lt;aside aria-label=&quot;Are we drifting here? — The split, enforced&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Are we drifting here? — The split, enforced&lt;/p&gt;&lt;div&gt;&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; split is only a design decision until a machine makes it load-bearing.&lt;/p&gt;&lt;p&gt;On the backend, the seam that matters is between the handler and the repo — the same clean separation the &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; distinction declares between identity and wire form.&lt;/p&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; rules in &lt;code dir=&quot;auto&quot;&gt;apps/api/.golangci.yml&lt;/code&gt; make that seam structural rather than conventional.&lt;/p&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;handler-no-db-driver&lt;/code&gt; means a &lt;code dir=&quot;auto&quot;&gt;handler.go&lt;/code&gt; file cannot import &lt;code dir=&quot;auto&quot;&gt;pgx&lt;/code&gt; — the handler has to speak to the database through the interface the repo exposes, not by reaching past it.&lt;/p&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;repo-no-transport&lt;/code&gt; means a &lt;code dir=&quot;auto&quot;&gt;repo.go&lt;/code&gt; file cannot import &lt;code dir=&quot;auto&quot;&gt;connectrpc.com/connect&lt;/code&gt; — the repo returns domain errors, and the handler maps them to Connect codes at the boundary.&lt;/p&gt;&lt;p&gt;Together these two rules ensure the only path from transport to storage passes through a typed interface: the &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt; side and the &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt; side stay separated by design and by enforcement.&lt;/p&gt;&lt;p&gt;One carve-out, stated plainly: &lt;code dir=&quot;auto&quot;&gt;internal/programs&lt;/code&gt; runs raw &lt;code dir=&quot;auto&quot;&gt;net/http&lt;/code&gt; and has no repo seam yet — it is excluded from both rules in &lt;code dir=&quot;auto&quot;&gt;.golangci.yml&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;TODO(programs-rpc)&lt;/code&gt; until the Connect-RPC rewrite is done.&lt;/p&gt;&lt;p&gt;On the frontend, &lt;code dir=&quot;auto&quot;&gt;defineFixtures&lt;/code&gt; is the vocabulary-primitive pattern in different clothes.&lt;/p&gt;&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;defineFixtures&lt;/code&gt; collection — &lt;code dir=&quot;auto&quot;&gt;loading&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ready&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;empty&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; — is a closed, named set of product states declared in one place, exactly the way an enum manifest declares wire values.&lt;/p&gt;&lt;p&gt;Three gates keep the vocabulary honest.&lt;/p&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;tools/eslint-no-fixture-outside-route.mjs&lt;/code&gt;) fires when &lt;code dir=&quot;auto&quot;&gt;screen.tsx&lt;/code&gt; imports fixtures directly — currently lint-warn at scoreboard ~113, target error.&lt;/p&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/prefer-use-state-maybe-fixture&lt;/code&gt; fires when &lt;code dir=&quot;auto&quot;&gt;route.tsx&lt;/code&gt; calls &lt;code dir=&quot;auto&quot;&gt;useState(rosterFixtures.ready)&lt;/code&gt; directly, bypassing the preset injection that makes App Mode work.&lt;/p&gt;&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;check-fixture-registry-coverage.mjs&lt;/code&gt; fails the PR when a &lt;code dir=&quot;auto&quot;&gt;defineFixtures&lt;/code&gt; collection is missing from &lt;code dir=&quot;auto&quot;&gt;fixture-registry.ts&lt;/code&gt;, so no vocabulary can hide.&lt;/p&gt;&lt;p&gt;The enforcement cost differs: ESLint rules are file-granular and cheap to add; &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; rules are package-granular and need type information.&lt;/p&gt;&lt;p&gt;The concept is the same on both sides.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;This is a real vocabulary from our backend — the lifecycle of an uploaded video:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;enum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;../enums&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;VideoStatus&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Uploading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Uploading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Processing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Processing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Ready&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ready&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Ready&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;terminal&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Archived&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Failed&lt;/code&gt; carries one piece of kind-specific metadata — &lt;code dir=&quot;auto&quot;&gt;terminal: true&lt;/code&gt; — because some consumer cares that it is an end state. Everything else is pure identity and presentation.&lt;/p&gt;
&lt;p&gt;That file is the &lt;strong&gt;source&lt;/strong&gt;. It is the only place &lt;code dir=&quot;auto&quot;&gt;VideoStatus&lt;/code&gt; is defined. Every Go constant, every TypeScript union, every SQL &lt;code dir=&quot;auto&quot;&gt;CHECK&lt;/code&gt; constraint, every dropdown is a &lt;em&gt;projection&lt;/em&gt; of it. We will watch those projections get generated in &lt;a href=&quot;http://localhost/blog/are-we-drifting-4-enums&quot;&gt;Part 4: Enums as Shared Vocabulary&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-same-atom-wearing-different-metadata&quot;&gt;The same atom, wearing different metadata&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The reason this is worth elevating to a “primitive” is that it is not just enums.&lt;/p&gt;
&lt;p&gt;Look at three vocabularies from the same backend, side by side. They are obviously the same shape.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;enum&lt;/strong&gt; — identity, label, a flag:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Failed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;terminal&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;An &lt;strong&gt;error&lt;/strong&gt; — identity, label, plus the metadata a failure needs (a transport code, and the typed fields it carries):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UploadTooLarge&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;upload_too_large&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload exceeds size limit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;invalid_argument&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;asset_id&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;size_bytes&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max_bytes&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;metric&lt;/strong&gt; — identity, label, plus the metadata an instrument needs (its kind and its labels):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UploadIntentCreated&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;wire&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;media.upload.intent_created&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload intents created&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;instrument&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;outcome&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;accepted&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;rejected&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Three different concerns — a state machine, a failure mode, an observability signal. One structure: a closed set of entries, each with a &lt;code dir=&quot;auto&quot;&gt;name&lt;/code&gt;, a &lt;code dir=&quot;auto&quot;&gt;wire&lt;/code&gt;, a &lt;code dir=&quot;auto&quot;&gt;label&lt;/code&gt;, and a bag of kind-specific metadata.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An enum is the base vocabulary. An error is that base plus failure metadata. A metric is that base plus instrument metadata. Generalize any of them down and you get the enum back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once you see it, you cannot unsee it. Permissions are a vocabulary (identity plus an action and a resource). Config keys are a vocabulary (identity plus a type and a default). Event types, job kinds, plan tiers, notification channels — every closed named set that crosses a boundary is the same atom.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-matters-for-humans-and-agents&quot;&gt;Why this matters for humans and agents&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The unification is not academic tidiness. It collapses &lt;em&gt;N&lt;/em&gt; mental models into one — and that is worth the most when the contributor is an agent.&lt;/p&gt;
&lt;p&gt;Without it, every closed set is a bespoke situation: enums are declared one way, errors another, config a third, each with its own conventions and its own places to look. A contributor — human or model — has to learn each one, and an agent generating code has &lt;em&gt;N&lt;/em&gt; chances to invent a value inline because it did not know the convention for that particular kind.&lt;/p&gt;
&lt;p&gt;With it, there is exactly one rule, and it fits in a sentence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If it is a closed, named set of values that crosses a boundary, it is a vocabulary. Vocabularies live in manifests. Code uses the generated constants. Inventing a value inline is a lint failure, not a style nit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every repeated question now has a single answer. &lt;em&gt;What are the allowed values for X?&lt;/em&gt; — the manifest for X. &lt;em&gt;How do I add one?&lt;/em&gt; — add an entry, regenerate, commit. &lt;em&gt;How do I deprecate one?&lt;/em&gt; — mark it &lt;code dir=&quot;auto&quot;&gt;deprecated&lt;/code&gt; in the manifest. &lt;em&gt;Where is this value used?&lt;/em&gt; — follow the projections from the manifest.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;not-shared-vocabulary--a-canonical-language&quot;&gt;Not shared vocabulary — a canonical language&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;“Shared vocabulary” undersells what this is. A vocabulary declared once is the canonical language of the whole system, and it is spoken by three audiences at the same time: the &lt;strong&gt;systems&lt;/strong&gt; that serialize the value across a wire, the &lt;strong&gt;humans&lt;/strong&gt; who name it and argue about it in review, and the &lt;strong&gt;agents&lt;/strong&gt; that write against it.&lt;/p&gt;
&lt;p&gt;The third audience is the one that changes the stakes. An agent does not only inherit the vocabulary by compiling against generated constants — it can &lt;em&gt;discover&lt;/em&gt; it. The same closed sets are reachable at runtime: an agent asks for the live set of backlog owners or statuses over MCP rather than hard-coding them, and the operations a service exposes are themselves introspectable, so the agent reads the contract instead of guessing it. The vocabulary is not just baked into the binary; it is a queryable, shared language.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A vocabulary that humans, systems, and agents all draw from — and that agents can discover at runtime — is not a naming convention. It is the &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Canonical vocabulary mesh&quot; data-term-general=&quot;The shared language of a system, spanning three audiences at once: the systems that serialize a concept, the humans who name and discuss it, and the agents that discover and use it. A tagged vocabulary is the atom; the mesh is what you get when every closed set is declared once and reachable everywhere — compiled into code and, crucially, discoverable at runtime. &amp;#x22;Shared vocabulary&amp;#x22; undersells it: it is the canonical language the whole system speaks.&quot; data-term-liftmere=&quot;Vocabularies are declared once (buildmere manifests; the backlog enums.mjs single source) and projected into Go/TS/Zod/SQL, AND exposed for runtime discovery over MCP — backlog_list_enums, the op-server operations.list / operations.get introspection — so an agent queries the live vocabulary instead of guessing it.&quot; data-term-href=&quot;/reference/glossary#vocabulary-mesh&quot;&gt;canonical language mesh&lt;/button&gt; the whole codebase speaks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is why the atom matters out of proportion to its size. Get the closed-set-declared-once right, and you are not deduplicating strings — you are giving every participant, silicon or human, the same words.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-velocity-payoff&quot;&gt;The velocity payoff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The coherence check this removes is the one nobody schedules and everybody pays: &lt;em&gt;did the value I just used actually exist, spelled exactly that way, on every side that has to agree about it?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When that check is a lint rule reading from a manifest, the answer is structural. An agent can add a status, regenerate, and ship — and the moment it references a value that is not in the vocabulary, the build says so, before review, before production.&lt;/p&gt;
&lt;p&gt;The expensive part was never typing the enum. It was the standing tax of &lt;em&gt;trusting&lt;/em&gt; that the six copies still agreed. Naming the atom is what lets a machine pay that tax for you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We have the atom. &lt;a href=&quot;http://localhost/blog/are-we-drifting-3-buildmere&quot;&gt;Part 3: buildmere, a Codegen Kernel&lt;/a&gt; introduces the machine that turns it into code on every side: &lt;strong&gt;buildmere&lt;/strong&gt;, a small codegen kernel where each kind of vocabulary is a plugin — and where the drift gate is one &lt;code dir=&quot;auto&quot;&gt;make&lt;/code&gt; target.&lt;/p&gt;</content:encoded></item><item><title>Are We Drifting? — Part 1: The Drift Problem</title><link>http://localhost/blog/are-we-drifting-1-the-drift-problem/</link><guid isPermaLink="true">http://localhost/blog/are-we-drifting-1-the-drift-problem/</guid><description>When generation is cheap, the bottleneck stops being how fast you write code and becomes how reliably it all still agrees with itself. This series asks one question of every layer in our stack — are we drifting? — and shows the shape of the answer.

</description><pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;are-we-drifting--part-1-the-drift-problem&quot;&gt;Are We Drifting? — Part 1: The Drift Problem&lt;/h1&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;the-question-this-series-keeps-asking&quot;&gt;The question this series keeps asking&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Pick any concept your system knows about.&lt;/p&gt;
&lt;p&gt;A project status. An error. A permission. A config key. The shape of a “create workout” request.&lt;/p&gt;
&lt;p&gt;Now count how many places that one concept is written down.&lt;/p&gt;
&lt;p&gt;The database has a column for it. The domain model has a field. The API contract has a message. The frontend has a TypeScript type, a dropdown, a validation rule. The event log has a payload. The docs have a table. A test has a fixture. An agent has an instruction.&lt;/p&gt;
&lt;p&gt;That is not one concept. That is one concept and a dozen copies of it.&lt;/p&gt;
&lt;p&gt;And copies drift.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are we drifting?&lt;/strong&gt; is the only question that matters once code is cheap to produce. This series asks it of every layer in our stack and shows the same answer each time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;what-we-mean-by-drift&quot;&gt;What we mean by drift&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Drift&quot; data-term-general=&quot;When two representations of the same concept stop agreeing. Usually slow and silent — a renamed field, a new value added on one side, a generated file edited by hand. The cumulative cost: no single representation can be trusted as authoritative, so every change requires re-checking all of them by hand.&quot; data-term-liftmere=&quot;Caught at PR time by the gate wall: check-vocab, check-sqlc, check-mocks, liftmere/* ESLint rules, buf lint, make agents-check, and others.&quot; data-term-href=&quot;/reference/glossary#drift&quot;&gt;Drift&lt;/button&gt; is what happens when two representations of the same concept stop agreeing.&lt;/p&gt;
&lt;p&gt;It is rarely dramatic. It is a slow accumulation of small disagreements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The backend rejects an enum value the frontend dropdown happily offers.&lt;/li&gt;
&lt;li&gt;An error means &lt;code dir=&quot;auto&quot;&gt;404&lt;/code&gt; in one handler and &lt;code dir=&quot;auto&quot;&gt;409&lt;/code&gt; in another, because someone guessed.&lt;/li&gt;
&lt;li&gt;The DB column is &lt;code dir=&quot;auto&quot;&gt;snake_case&lt;/code&gt;, the API field is &lt;code dir=&quot;auto&quot;&gt;camelCase&lt;/code&gt;, and a mapping function in the middle silently swallows the one that was renamed.&lt;/li&gt;
&lt;li&gt;A screen imports a fixture that no longer matches the real product state.&lt;/li&gt;
&lt;li&gt;A handler quietly diverges from the contract it was generated against.&lt;/li&gt;
&lt;li&gt;The docs describe a flag that was removed two sprints ago.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these is a crash. Each is a small lie the codebase tells about itself.&lt;/p&gt;
&lt;p&gt;Here is what those lies look like when they are actually caught — by name, in this codebase:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;The drift&lt;/th&gt;&lt;th&gt;The gate that catches it&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;A screen imports its own fixture&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt; — lint-error, PR fails&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A component uses &lt;code dir=&quot;auto&quot;&gt;#0f0f17&lt;/code&gt; instead of a design token&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-raw-hex-color&lt;/code&gt; — lint-error, ambient&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A handler returns &lt;code dir=&quot;auto&quot;&gt;fmt.Errorf(&quot;not found&quot;)&lt;/code&gt; directly&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-connect-errors&lt;/code&gt; — CI script, build fails&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A committed &lt;code dir=&quot;auto&quot;&gt;.gen.go&lt;/code&gt; drifts from its YAML manifest&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;make check-vocab&lt;/code&gt; — buildmere drift gate, exits non-zero&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A generated feature registry goes stale&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;generate-features --check&lt;/code&gt; — codegen gate, PR fails&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A migration file is edited after it was applied&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;check-migrations.sh&lt;/code&gt; — append-only enforcement&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A handler’s interface assertion fails after a proto change&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;var _ ServiceHandler = (*Service)(nil)&lt;/code&gt; — compile error&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A smart quote &lt;code dir=&quot;auto&quot;&gt;&apos;&lt;/code&gt; slips into a TS string literal&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-smart-quotes&lt;/code&gt; — ambient lint-error&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Each row is a real rule in this repo. Each one was built because the drift it catches had already happened, or was structurally guaranteed to happen once generation got fast enough.&lt;/p&gt;
&lt;p&gt;Three of those rows have stories worth naming.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-smart-quotes&lt;/code&gt; exists because a curly quote — &lt;code dir=&quot;auto&quot;&gt;&apos;&lt;/code&gt; (U+2018) — slipped into a TypeScript string literal inside a fixture file.&lt;/p&gt;
&lt;p&gt;The JS parser accepted it silently; the rendered copy looked correct to the eye.&lt;/p&gt;
&lt;p&gt;The corruption only surfaced when the fixture value was compared programmatically.&lt;/p&gt;
&lt;p&gt;The rule is ambient — it runs across all source, not just component files — because the failure mode requires no special structure to trigger: any file, any context, same silent breakage.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;liftmere/no-fixture-outside-route&lt;/code&gt; fires today at &lt;code dir=&quot;auto&quot;&gt;warn&lt;/code&gt;, not &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt;, because there are still ~113 violations in &lt;code dir=&quot;auto&quot;&gt;apps/client/src&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rule exists and the violations are known; the flip to &lt;code dir=&quot;auto&quot;&gt;error&lt;/code&gt; is gated on reaching zero, and the count is a number a single &lt;code dir=&quot;auto&quot;&gt;eslint&lt;/code&gt; run prints — the burndown is visible, not a guess.&lt;/p&gt;
&lt;p&gt;The frontend’s burndown is legible. The backend is leaner, and the next guardrails land there.&lt;/p&gt;
&lt;p&gt;On the backend, exactly one sub-system is genuinely gated today: the compile assertion &lt;code dir=&quot;auto&quot;&gt;var _ WorkoutServiceHandler = (*Service)(nil)&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;handler.go&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Every other guardrail in the table — &lt;code dir=&quot;auto&quot;&gt;buf lint&lt;/code&gt;, the &lt;code dir=&quot;auto&quot;&gt;forbidigo&lt;/code&gt; ban on bare &lt;code dir=&quot;auto&quot;&gt;fmt.Errorf&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; for domain layering — is documented convention that does not yet run in CI.&lt;/p&gt;
&lt;p&gt;The frontend runs 13 custom ESLint rules on every PR; the backend runs one compile check.&lt;/p&gt;
&lt;p&gt;That asymmetry is not a confession — it is an ordered work list. The code already follows the conventions; the work is to make them fail the build, by wiring &lt;code dir=&quot;auto&quot;&gt;buf lint&lt;/code&gt;, the &lt;code dir=&quot;auto&quot;&gt;forbidigo&lt;/code&gt; error ban, the &lt;code dir=&quot;auto&quot;&gt;depguard&lt;/code&gt; layering rules, and &lt;code dir=&quot;auto&quot;&gt;sloglint&lt;/code&gt; into the same CI gate the frontend rules already run in. The conventions are real today; turning each into a gate is the next increment, and the backend guardrails come in that order.&lt;/p&gt;
&lt;p&gt;Part of why the gap exists is cost: ESLint rules are file-granular and syntactic — an hour of work, precise triggers.&lt;/p&gt;
&lt;p&gt;Go’s equivalents (&lt;code dir=&quot;auto&quot;&gt;go/analysis&lt;/code&gt; analyzers, most golangci-lint linters) are package-granular and often need type information, which puts a backend guardrail closer to a day of work than an hour.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A documented rule that doesn’t run in CI is theatre. A rule that fails the build is a guardrail. The work is turning the first into the second, backend first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The same pattern that makes the frontend rules fine-grained — opt-in by structure, dormant until you adopt the shape — holds on both stacks; it just costs more per guardrail on the backend side.&lt;/p&gt;
&lt;p&gt;The cost is not the individual bug. The cost is that, after enough of them, nobody trusts any single representation to be authoritative — so every change requires re-checking all of them by hand.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-got-worse-not-better&quot;&gt;Why this got worse, not better&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Software used to be bottlenecked by how fast humans could write code.&lt;/p&gt;
&lt;p&gt;That bottleneck made deep governance feel expensive. Writing a linter, a code generator, a schema registry — these were investments you made only at significant scale, because the thing they protected against (drift) accumulated slowly, at human typing speed.&lt;/p&gt;
&lt;p&gt;Agentic workflows remove that bottleneck.&lt;/p&gt;
&lt;p&gt;Agents make generation cheap. They make refactoring cheap. They make the repetitive infrastructure that surrounds a feature cheap.&lt;/p&gt;
&lt;p&gt;But cheap generation does not reduce drift. It accelerates it.&lt;/p&gt;
&lt;p&gt;An agent can produce six new representations of a concept in the time it used to take a human to write one — and unless something forces those six to agree, the agent has just multiplied the surface area where they can disagree.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When generation is cheap, the advantage moves from &lt;em&gt;how fast can we write code?&lt;/em&gt; to &lt;em&gt;how reliably can we keep it all agreeing with itself?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the thesis of an earlier post, &lt;a href=&quot;http://localhost/blog/vocabulary-driven-governance&quot; title=&quot;Vocabulary-Driven Governance&quot;&gt;Vocabulary-Driven Governance&lt;/a&gt;. That post argued the case. This series is the proof: the concrete, shipped systems we use to answer “are we drifting?” with &lt;em&gt;no&lt;/em&gt; on every layer we can reach.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-the-rails-are-the-accelerant&quot;&gt;Why the rails are the accelerant&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The instinct is to file all of this under “quality” — a tax you pay to keep the codebase tidy, drawn from the same budget you would rather spend on shipping.&lt;/p&gt;
&lt;p&gt;In the agentic era that instinct is not merely incomplete. It is backwards, for two reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First: the rails are what let you move fast.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When writing code is cheap, and the patterns you would have reached for are general knowledge an agent already holds, typing and expertise stop being the bottleneck.&lt;/p&gt;
&lt;p&gt;What stays scarce is coherence — whether the thing you just generated still agrees with the dozen others that describe the same concept.&lt;/p&gt;
&lt;p&gt;Cheap generation does not pay that cost down. It runs the bill up faster, because there is more being produced that can disagree.&lt;/p&gt;
&lt;p&gt;So the teams that move fastest are not the ones generating the most code. They are the ones who can generate &lt;em&gt;confidently&lt;/em&gt;: structure says where a thing goes, a linter refuses the shapes that would drift, and a manifest is the one place a concept is defined while the many places it appears are produced from it.&lt;/p&gt;
&lt;p&gt;Those three let you accept a large diff from an agent and trust it, because anything incoherent fails at PR time instead of in production three weeks later.&lt;/p&gt;
&lt;p&gt;That trust &lt;em&gt;is&lt;/em&gt; the velocity. Not the typing speed. The not-having-to-recheck.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second: the rails themselves just got cheap.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The old reason deep governance was rare is the one named above — building and maintaining it was expensive. A custom linter, a code generator, a schema registry, the manifest plumbing: real engineering cost, justified only at scale.&lt;/p&gt;
&lt;p&gt;That assumption no longer holds.&lt;/p&gt;
&lt;p&gt;The same economics that made generation cheap also collapsed the cost of the cure — and that inversion is why now is the right moment, not just a good one. An agent can write the lint rule, add a codegen kind, wire the &lt;code dir=&quot;auto&quot;&gt;--check&lt;/code&gt; gate, and backfill the manifest in an afternoon.&lt;/p&gt;
&lt;p&gt;So the calculus inverts twice over: the structure is now cheap to lay &lt;em&gt;and&lt;/em&gt; cheap to maintain, &lt;em&gt;and&lt;/em&gt; it is the thing that unlocks the speed. The same force that creates the drift problem is what funds the solution.&lt;/p&gt;
&lt;figure&gt; &lt;svg viewBox=&quot;0 0 720 346&quot; role=&quot;img&quot; aria-labelledby=&quot;cost-title cost-desc&quot;&gt; &lt;title id=&quot;cost-title&quot;&gt;The cost of building the rails, then and now&lt;/title&gt; &lt;desc id=&quot;cost-desc&quot;&gt;
Building a custom linter, a codegen kind, or a drift gate used to take
      weeks to a quarter; cheap code generation collapses each to hours or an
      afternoon.
&lt;/desc&gt; &lt;!-- legend --&gt; &lt;rect x=&quot;196&quot; y=&quot;28&quot; width=&quot;14&quot; height=&quot;14&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;218&quot; y=&quot;40&quot;&gt;then — before generation was cheap&lt;/text&gt; &lt;rect x=&quot;446&quot; y=&quot;28&quot; width=&quot;14&quot; height=&quot;14&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;468&quot; y=&quot;40&quot;&gt;now&lt;/text&gt; &lt;g&gt; &lt;text x=&quot;24&quot; y=&quot;90&quot;&gt;A custom linter&lt;/text&gt; &lt;rect x=&quot;196&quot; y=&quot;64&quot; width=&quot;204&quot; height=&quot;18&quot; rx=&quot;3&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;410&quot; y=&quot;78&quot;&gt;weeks&lt;/text&gt; &lt;rect x=&quot;196&quot; y=&quot;96&quot; width=&quot;30.599999999999998&quot; height=&quot;18&quot; rx=&quot;3&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;236.6&quot; y=&quot;110&quot;&gt;hours&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;text x=&quot;24&quot; y=&quot;176&quot;&gt;A codegen kind&lt;/text&gt; &lt;rect x=&quot;196&quot; y=&quot;150&quot; width=&quot;323&quot; height=&quot;18&quot; rx=&quot;3&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;529&quot; y=&quot;164&quot;&gt;a quarter&lt;/text&gt; &lt;rect x=&quot;196&quot; y=&quot;182&quot; width=&quot;47.6&quot; height=&quot;18&quot; rx=&quot;3&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;253.6&quot; y=&quot;196&quot;&gt;an afternoon&lt;/text&gt; &lt;/g&gt;&lt;g&gt; &lt;text x=&quot;24&quot; y=&quot;262&quot;&gt;A drift gate&lt;/text&gt; &lt;rect x=&quot;196&quot; y=&quot;236&quot; width=&quot;136&quot; height=&quot;18&quot; rx=&quot;3&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;342&quot; y=&quot;250&quot;&gt;days&lt;/text&gt; &lt;rect x=&quot;196&quot; y=&quot;268&quot; width=&quot;17&quot; height=&quot;18&quot; rx=&quot;3&quot;&gt;&lt;/rect&gt; &lt;text x=&quot;223&quot; y=&quot;282&quot;&gt;minutes&lt;/text&gt; &lt;/g&gt; &lt;/svg&gt; &lt;figcaption&gt; Conceptual — relative effort, not measured data. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;Constraints are not the brake on cheap generation. They are what makes it safe to trust — and trust is what lets you go fast. “Governance is expensive” is an old-world assumption that no longer holds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Keep this in view through every part that follows: each guardrail we describe earns its place by &lt;em&gt;removing&lt;/em&gt; a manual coherence check, so the cheap part — the generation — can run at full speed without leaving a mess behind it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-shape-of-every-answer&quot;&gt;The shape of every answer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is one pattern underneath everything that follows. It does not change from layer to layer; only the materials change.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;One declarative source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; many generated projections&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&gt; drift gates keep them honest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Read it as three commitments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;One source.&lt;/strong&gt; A concept is &lt;em&gt;declared once&lt;/em&gt;, in a place that is obviously canonical — a manifest, a contract, a single definition. Not written by hand in twelve places.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Many projections.&lt;/strong&gt; Every representation the system needs — Go types, TypeScript constants, validators, SQL constraints, docs, agent rules — is &lt;em&gt;generated&lt;/em&gt; from that one source. A projection is never the truth; it is a view of the truth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Drift gate&quot; data-term-general=&quot;A check that runs in CI and fails the build when a committed projection diverges from what its source would generate. The load-bearing leg of the governance model — without it, &amp;#x22;generated&amp;#x22; is just a suggestion.&quot; data-term-liftmere=&quot;make check-vocab (buildmere), generate-features --check (feature registry), make check-sqlc, make check-mocks, make agents-check. All exit non-zero on any byte of drift.&quot; data-term-href=&quot;/reference/glossary#drift-gate&quot;&gt;Drift gates&lt;/button&gt;.&lt;/strong&gt; A check runs in CI that fails the build if any committed &lt;button type=&quot;button&quot; aria-haspopup=&quot;dialog&quot; data-term-name=&quot;Projection&quot; data-term-general=&quot;An artifact generated from a single source — a view of the truth, not the truth itself. A projection can always be regenerated from its source; if it cannot, it is a copy, not a projection. Copies drift; projections are kept honest by a drift gate.&quot; data-term-liftmere=&quot;Generated .gen.go files (buildmere), features.generated.ts (generate-features), Postgres read-model tables (event log replay). Each has a paired drift gate.&quot; data-term-href=&quot;/reference/glossary#projection&quot;&gt;projection&lt;/button&gt; no longer matches what the source would generate. The gate is the load-bearing part. Without it, “generated” is just a suggestion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A concept that follows this shape cannot drift, because there is only one place it can be changed, and the gate refuses to let the copies disagree with it.&lt;/p&gt;
&lt;p&gt;That is the whole game. The rest of this series is what it looks like at each altitude.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-three-altitudes&quot;&gt;The three altitudes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The same shape recurs at three very different levels of the stack. Watching it repeat is the point.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vocabularies.&lt;/strong&gt; A closed, named set of values — an enum, an error, a config key, a metric — declared in a YAML manifest and projected into Go, TypeScript, Zod, JSON Schema, SQL, and docs. Our toolkit for this is called &lt;code dir=&quot;auto&quot;&gt;buildmere&lt;/code&gt;, and it is a kernel with pluggable “kinds.” Parts 2 through 6.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operations.&lt;/strong&gt; A single operation, defined once with its input and output schemas, exposed simultaneously as a REST endpoint, an MCP tool, and a CLI command — one handler behind all three. This is the &lt;code dir=&quot;auto&quot;&gt;op-server&lt;/code&gt; layer. Part 9.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents.&lt;/strong&gt; A single agent manifest, rendered into the formats Claude Code and Cursor each expect, with provider-agnostic execution tiers resolved at dispatch time. This is the Agent OS. Part 10.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Between them sit two more pieces: &lt;strong&gt;projected truth&lt;/strong&gt; — read models rebuilt from an append-only event log, where the log is the source and every table is a projection (Part 8) — and the &lt;strong&gt;edge of the pattern&lt;/strong&gt;, where the shape goes next and what it unlocks (Part 14).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-note-on-direction&quot;&gt;A note on direction&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This series shows the pattern and the trail markers for where it goes next.&lt;/p&gt;
&lt;p&gt;When a guardrail runs in CI, you will see the rule that fails the build. When a guardrail is the natural next step, you will see exactly what it looks like and where it lands.&lt;/p&gt;
&lt;p&gt;The shape — one source, many projections, drift gates — is a direction we have walked a long way down, and the trail keeps going.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-2-the-tagged-vocabulary&quot;&gt;Part 2: The Tagged Vocabulary&lt;/a&gt; starts at the smallest, most concrete unit: the &lt;strong&gt;tagged vocabulary&lt;/strong&gt; — the humble idea that a closed set of named values, declared once, is the atom the entire pattern is built from.&lt;/p&gt;
&lt;p&gt;Everything else is that idea, scaled up.&lt;/p&gt;</content:encoded></item><item><title>Vocabulary-Driven Governance: The Unlock for Agentic Software Development</title><link>http://localhost/blog/vocabulary-driven-governance/</link><guid isPermaLink="true">http://localhost/blog/vocabulary-driven-governance/</guid><description>The next engineering platform is a vocabulary engine — define codebase concepts once, generate their representations everywhere, and govern usage through policy, linting, tests, and agent behavior.

</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;vocabulary-driven-governance-the-unlock-for-agentic-software-development&quot;&gt;Vocabulary-Driven Governance: The Unlock for Agentic Software Development&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;This is the premise — Part 0 of the “Are We Drifting?” series, where we lay out the thesis the rest of the series puts to the test.&lt;/p&gt;
&lt;p&gt;Everything that follows is grounded in one real codebase — ours. You will not have access to it, and that is fine: the file paths, the &lt;code dir=&quot;auto&quot;&gt;make&lt;/code&gt; targets, and the package names are not instructions to run, they are a worked example. The thing that travels is the &lt;em&gt;shape&lt;/em&gt;, not our spelling of it. Read the specifics as proof that the pattern survives contact with a production system, then map it onto your own stack — the &lt;a href=&quot;http://localhost/reference/glossary/&quot;&gt;glossary&lt;/a&gt; gives every core term in two lenses, the general concept and our particular implementation, for exactly that reason.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;core-thesis&quot;&gt;Core Thesis&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In the agentic era, the highest-leverage engineering systems are not the ones that merely help teams write code faster.&lt;/p&gt;
&lt;p&gt;They are the systems that define, generate, constrain, verify, and document code so that fast code remains coherent.&lt;/p&gt;
&lt;p&gt;When code is cheap, &lt;strong&gt;constraints become the product development advantage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When generation is cheap, &lt;strong&gt;governance becomes the speed unlock&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Two forces move in opposite directions as generation gets cheaper. The cost of &lt;em&gt;writing&lt;/em&gt; code falls toward zero. The cost of &lt;em&gt;ungoverned drift&lt;/em&gt; — the cumulative tax of representations that no longer agree — rises, because cheap generation produces more of them, faster. Where the two cross is the whole argument of this series.&lt;/p&gt;
&lt;figure&gt; &lt;svg viewBox=&quot;0 0 720 400&quot; role=&quot;img&quot; aria-labelledby=&quot;econ-title econ-desc&quot;&gt; &lt;title id=&quot;econ-title&quot;&gt;The inversion&lt;/title&gt; &lt;desc id=&quot;econ-desc&quot;&gt;
As code generation gets cheaper, the cost of writing code falls while the
      cost of ungoverned drift rises; where the two curves cross, governance
      becomes the higher-leverage investment.
&lt;/desc&gt; &lt;!-- baseline + axis --&gt; &lt;line x1=&quot;56&quot; y1=&quot;336&quot; x2=&quot;696&quot; y2=&quot;336&quot;&gt;&lt;/line&gt; &lt;line x1=&quot;56&quot; y1=&quot;48&quot; x2=&quot;56&quot; y2=&quot;336&quot;&gt;&lt;/line&gt; &lt;!-- y-axis label --&gt; &lt;text x=&quot;20&quot; y=&quot;192&quot; transform=&quot;rotate(-90 20 192)&quot;&gt;cost · risk&lt;/text&gt; &lt;!-- x-axis label --&gt; &lt;text x=&quot;376&quot; y=&quot;380&quot;&gt;generation gets cheaper →&lt;/text&gt; &lt;!-- falling curve: cost to write code --&gt; &lt;path d=&quot;M 56 96 C 250 118, 430 286, 696 300&quot;&gt;&lt;/path&gt; &lt;!-- rising curve: cost of ungoverned drift --&gt; &lt;path d=&quot;M 56 300 C 250 286, 430 110, 696 96&quot;&gt;&lt;/path&gt; &lt;!-- crossover marker --&gt; &lt;line x1=&quot;376&quot; y1=&quot;120&quot; x2=&quot;376&quot; y2=&quot;336&quot;&gt;&lt;/line&gt; &lt;circle cx=&quot;376&quot; cy=&quot;198&quot; r=&quot;6&quot;&gt;&lt;/circle&gt; &lt;text x=&quot;376&quot; y=&quot;104&quot;&gt;governance becomes the unlock&lt;/text&gt; &lt;!-- curve end labels --&gt; &lt;text x=&quot;690&quot; y=&quot;318&quot;&gt;cost to write code&lt;/text&gt; &lt;text x=&quot;690&quot; y=&quot;84&quot;&gt;cost of ungoverned drift&lt;/text&gt; &lt;/svg&gt; &lt;figcaption&gt; Conceptual — the shape of the shift, not measured data. &lt;/figcaption&gt; &lt;/figure&gt;
&lt;p&gt;The new edge is not just better coding. The new edge is better &lt;strong&gt;codebase governance&lt;/strong&gt;: the ability to create shared vocabulary, generate consistent artifacts from that vocabulary, and enforce correct usage across humans, agents, packages, services, and time.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-shift&quot;&gt;The Shift&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Software development used to be bottlenecked by implementation speed.&lt;/p&gt;
&lt;p&gt;Writing code, refactoring code, creating linters, maintaining code generators, writing documentation, and enforcing structure were all expensive enough that teams often avoided deep governance unless they were operating at significant scale.&lt;/p&gt;
&lt;p&gt;That changes in the age of agentic workflows.&lt;/p&gt;
&lt;p&gt;Agents make code generation cheaper. They make refactoring cheaper. They make repetitive infrastructure cheaper. But they also increase the need for strong boundaries, because unconstrained generation can create drift, duplication, inconsistency, and architectural decay faster than human-only development ever could.&lt;/p&gt;
&lt;p&gt;The advantage moves from:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“How fast can we write code?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“How reliably can we govern how code comes into existence?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;governing-module&quot;&gt;Governing Module&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;strong&gt;governing module&lt;/strong&gt; is the system responsible for defining what valid codebase behavior means and enforcing that validity through generation, policy, linting, testing, documentation, and agent instructions.&lt;/p&gt;
&lt;p&gt;It has two complementary sides.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-generative-side&quot;&gt;1. Generative Side&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The generative side creates valid shapes from canonical definitions.&lt;/p&gt;
&lt;p&gt;It can generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backend types&lt;/li&gt;
&lt;li&gt;Frontend types&lt;/li&gt;
&lt;li&gt;Runtime validators&lt;/li&gt;
&lt;li&gt;API route definitions&lt;/li&gt;
&lt;li&gt;API handlers&lt;/li&gt;
&lt;li&gt;Config wiring&lt;/li&gt;
&lt;li&gt;Environment variable accessors&lt;/li&gt;
&lt;li&gt;Error declarations&lt;/li&gt;
&lt;li&gt;Metric names and tags&lt;/li&gt;
&lt;li&gt;Logging tags and message templates&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Test fixtures&lt;/li&gt;
&lt;li&gt;Agent instructions&lt;/li&gt;
&lt;li&gt;Migration helpers&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;2-enforcement-side&quot;&gt;2. Enforcement Side&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The enforcement side prevents invalid shapes from entering the system.&lt;/p&gt;
&lt;p&gt;It can enforce:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Import boundaries&lt;/li&gt;
&lt;li&gt;Dependency rules&lt;/li&gt;
&lt;li&gt;Package ownership&lt;/li&gt;
&lt;li&gt;Error usage policies&lt;/li&gt;
&lt;li&gt;Metric naming rules&lt;/li&gt;
&lt;li&gt;Config access rules&lt;/li&gt;
&lt;li&gt;Logging structure&lt;/li&gt;
&lt;li&gt;API route conventions&lt;/li&gt;
&lt;li&gt;Test requirements&lt;/li&gt;
&lt;li&gt;Pre-commit checks&lt;/li&gt;
&lt;li&gt;CI policies&lt;/li&gt;
&lt;li&gt;Agent behavior policies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Generation gives consistency.&lt;/p&gt;
&lt;p&gt;Enforcement gives trust.&lt;/p&gt;
&lt;p&gt;Together, they create the governing loop.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Define vocabulary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Generate artifacts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enforce usage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Detect drift&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Update manifest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Regenerate artifacts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;vocabulary-as-the-umbrella-concept&quot;&gt;Vocabulary as the Umbrella Concept&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vocabulary is the name of the game.&lt;/p&gt;
&lt;p&gt;But vocabulary does not simply mean naming.&lt;/p&gt;
&lt;p&gt;In this model, vocabulary means the shared generative contract for a codebase concept.&lt;/p&gt;
&lt;p&gt;A config is not just a backend object.&lt;/p&gt;
&lt;p&gt;A metric is not just a string.&lt;/p&gt;
&lt;p&gt;An error is not just a class.&lt;/p&gt;
&lt;p&gt;An API route is not just a handler.&lt;/p&gt;
&lt;p&gt;Each one is a vocabulary item with a contract.&lt;/p&gt;
&lt;p&gt;That contract can be used to generate, validate, document, and enforce the concept across many environments.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;vocabulary-driven-development&quot;&gt;Vocabulary-Driven Development&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The old model looked like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We manually write the backend version, manually write the frontend version, manually document it, manually enforce it, manually teach developers and agents how to use it, and then hope everything stays aligned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The new model looks like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We define the vocabulary once, generate the artifacts everywhere, and enforce that all usage flows through the vocabulary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is &lt;strong&gt;vocabulary-driven development&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;More specifically, it is &lt;strong&gt;manifest-driven vocabulary governance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The manifest becomes a semantic source of truth.&lt;/p&gt;
&lt;p&gt;It does not merely say, “generate this file.”&lt;/p&gt;
&lt;p&gt;It says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“This is a concept the codebase recognizes.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;manifest-driven-governance&quot;&gt;Manifest-Driven Governance&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A manifest is a centralized definition of a codebase concept.&lt;/p&gt;
&lt;p&gt;That definition can then be translated into multiple worlds.&lt;/p&gt;
&lt;p&gt;For example, an environment variable manifest might define:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;STRIPE_WEBHOOK_SECRET&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;backend&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;owner&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;billing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Secret used to verify Stripe webhook signatures.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;exposedToFrontend&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From that one definition, the system can generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backend environment parser&lt;/li&gt;
&lt;li&gt;Typed config access&lt;/li&gt;
&lt;li&gt;Markdown documentation&lt;/li&gt;
&lt;li&gt;Secret management checklist&lt;/li&gt;
&lt;li&gt;CI validation&lt;/li&gt;
&lt;li&gt;Deployment validation&lt;/li&gt;
&lt;li&gt;Test fixture requirements&lt;/li&gt;
&lt;li&gt;Agent rules that say not to access &lt;code dir=&quot;auto&quot;&gt;process.env&lt;/code&gt; directly&lt;/li&gt;
&lt;li&gt;Lint rules requiring use of &lt;code dir=&quot;auto&quot;&gt;config.stripe.webhookSecret&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is stronger than ordinary code generation.&lt;/p&gt;
&lt;p&gt;It is vocabulary as infrastructure.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-matters-for-agents&quot;&gt;Why This Matters for Agents&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Agents are powerful because they can produce large amounts of code quickly.&lt;/p&gt;
&lt;p&gt;But agents become dramatically more useful when the codebase has strong rails.&lt;/p&gt;
&lt;p&gt;A vocabulary manifest gives agents a map.&lt;/p&gt;
&lt;p&gt;Linters, tests, pre-commit hooks, policies, and generated contracts give agents walls.&lt;/p&gt;
&lt;p&gt;The question becomes less:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Can the agent implement this?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and more:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Is there a vocabulary contract that makes this implementation safe, repeatable, observable, documented, and compatible?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the agent-native framing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-new-development-loop&quot;&gt;The New Development Loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In a governed, vocabulary-driven system, every new codebase concept has a canonical path.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New error? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New metric? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New route? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New config? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New permission? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New event? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;New integration? Add to vocabulary.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Once added to the vocabulary, the surrounding ecosystem can be generated automatically.&lt;/p&gt;
&lt;p&gt;That includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Types&lt;/li&gt;
&lt;li&gt;Runtime validation&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Fixtures&lt;/li&gt;
&lt;li&gt;Examples&lt;/li&gt;
&lt;li&gt;Policies&lt;/li&gt;
&lt;li&gt;Linters&lt;/li&gt;
&lt;li&gt;Agent instructions&lt;/li&gt;
&lt;li&gt;Migration scripts&lt;/li&gt;
&lt;li&gt;Observability hooks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This transforms the codebase from a pile of files into a governed semantic system.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;examples-of-vocabulary-surfaces&quot;&gt;Examples of Vocabulary Surfaces&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The easiest and highest-leverage surfaces for this kind of system are often the messy ones.&lt;/p&gt;
&lt;p&gt;These include:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;errors&quot;&gt;Errors&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Define canonical error codes, messages, metadata, ownership, retryability, and user-facing behavior.&lt;/p&gt;
&lt;p&gt;Generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Error classes&lt;/li&gt;
&lt;li&gt;API error responses&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Test fixtures&lt;/li&gt;
&lt;li&gt;Client-safe error mappings&lt;/li&gt;
&lt;li&gt;Lint rules preventing ad hoc errors&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;metrics&quot;&gt;Metrics&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Define metric names, tags, owners, cardinality rules, and dashboard metadata.&lt;/p&gt;
&lt;p&gt;Generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Metric constants&lt;/li&gt;
&lt;li&gt;Type-safe emitters&lt;/li&gt;
&lt;li&gt;Docs&lt;/li&gt;
&lt;li&gt;Dashboards&lt;/li&gt;
&lt;li&gt;Alerts&lt;/li&gt;
&lt;li&gt;Lint rules preventing raw metric strings&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;logging&quot;&gt;Logging&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Define structured log events, allowed fields, severity levels, and privacy constraints.&lt;/p&gt;
&lt;p&gt;Generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Log event helpers&lt;/li&gt;
&lt;li&gt;Redaction policies&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Lint rules preventing unstructured logs&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;api-routes&quot;&gt;API Routes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Define routes, methods, permissions, request schemas, response schemas, and ownership.&lt;/p&gt;
&lt;p&gt;Generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backend route bindings&lt;/li&gt;
&lt;li&gt;Frontend clients&lt;/li&gt;
&lt;li&gt;OpenAPI specs&lt;/li&gt;
&lt;li&gt;Markdown docs&lt;/li&gt;
&lt;li&gt;Validation middleware&lt;/li&gt;
&lt;li&gt;Test fixtures&lt;/li&gt;
&lt;li&gt;Agent implementation instructions&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;config-and-environment-variables&quot;&gt;Config and Environment Variables&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Define config keys, environment variables, runtime availability, ownership, defaults, and exposure rules.&lt;/p&gt;
&lt;p&gt;Generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backend config accessors&lt;/li&gt;
&lt;li&gt;Frontend-safe config representations&lt;/li&gt;
&lt;li&gt;Runtime validators&lt;/li&gt;
&lt;li&gt;Deployment checks&lt;/li&gt;
&lt;li&gt;Docs&lt;/li&gt;
&lt;li&gt;Lint rules preventing direct environment access&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-unlock&quot;&gt;The Unlock&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The unlock is the combination of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Agentic workflows&lt;/li&gt;
&lt;li&gt;Manifest-driven generation&lt;/li&gt;
&lt;li&gt;Codebase governance&lt;/li&gt;
&lt;li&gt;Shared vocabulary&lt;/li&gt;
&lt;li&gt;Enforcement through linters, tests, policies, and pre-commit checks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The winning teams will not simply be the teams with the most agents.&lt;/p&gt;
&lt;p&gt;They will be the teams with the best vocabulary layer.&lt;/p&gt;
&lt;p&gt;Because once vocabulary exists, agents and developers can move faster without creating chaos.&lt;/p&gt;
&lt;p&gt;The system can generate the correct shapes, enforce the correct boundaries, and keep the codebase coherent as it grows.&lt;/p&gt;
&lt;p&gt;That is the thesis. The rest of the series puts it to the test, starting with the problem it exists to solve.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost/blog/are-we-drifting-1-the-drift-problem/&quot; title=&quot;The Drift Problem&quot;&gt;Are We Drifting? Part 1: The Drift Problem&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Welcome to the Buildmere Engineering blog</title><link>http://localhost/blog/welcome/</link><guid isPermaLink="true">http://localhost/blog/welcome/</guid><description>A fresh, fast home for engineering writing — and an MCP server so agents can publish here too.

</description><pubDate>Sun, 31 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the first post on the &lt;strong&gt;Buildmere Engineering&lt;/strong&gt; blog.&lt;/p&gt;
&lt;p&gt;We stood up a dedicated hub — separate from the internal Docusaurus workspace — for durable
engineering writing: architecture notes, build practices, and posts like this one. It’s built
on Astro + Starlight, with static Pagefind search that stays instant as we add content.&lt;/p&gt;
&lt;p&gt;What’s a little different here: the hub ships its own &lt;strong&gt;MCP server&lt;/strong&gt;, so agents can draft,
edit, publish, and rebuild content as tools — the same operations model the rest of the stack
uses. Expect more posts as the engineering surface area grows.&lt;/p&gt;</content:encoded></item></channel></rss>