<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Writing</title><description>Essays and notes.</description><link>https://reubenbrooks.dev/</link><language>en-us</language><item><title>The LLM Does Not Know It Is Being Formally Verified</title><link>https://reubenbrooks.dev/blog/deductive-backpressure-why-test-feedback-isn-t-enough-for-ai-coding-loops/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/deductive-backpressure-why-test-feedback-isn-t-enough-for-ai-coding-loops/</guid><description>Behavioral constraints degrade under autonomy; structural ones do not. You do not need smarter models — you need smarter types.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The multi-tenant API demo that emerged from an autonomous LLM loop took a while to fully register with me. Eight implementation steps, zero gate failures, a formally verified authorization proof chain -- and the LLM never saw the Shen specification. It never reasoned about sequent calculus. It never thought about authorization invariants at all. It wrote ordinary Go code, the code compiled, and the types happened to enforce a formal proof chain. This distinction -- between an AI that understands formal verification and an AI that is structurally constrained into producing formally verified output -- matters more than any other technical detail in the system, and I think the implications extend considerably further than most people working in this space have yet recognized.&lt;/p&gt;
&lt;p&gt;The current wave of formal-verification-meets-AI research assumes the LLM should be the proof engineer. Lean Copilot automates roughly 74% of proof steps in Lean 4, requiring about two manual steps per proof on average. DeepSeek-Prover-V2 achieves what its authors describe as state-of-the-art theorem proving performance. ATLAS synthesizes Dafny programs with specifications and proofs, with fine-tuned models gaining significant improvements on DafnyBench. Self-Spec has the LLM generate its own pre- and postconditions before generating code. All of these approaches ask essentially the same question: can the LLM reason about formal logic? There is an implicit assumption in this line of research that I suspect is often not articulated clearly -- namely, that the path to formally verified AI-generated code must run through the AI&apos;s own capacity for logical reasoning. This seems natural enough, but it is worth examining whether the assumption is actually necessary, or whether it reflects a kind of intellectual path dependence inherited from how humans do formal verification.&lt;/p&gt;
&lt;p&gt;Shen-Backpressure asks a different question entirely: does the LLM need to reason about proofs at all? The five-gate pipeline does not ask the LLM to write proofs. It does not even show the LLM the formal specification. A human writes a Shen spec, roughly fifty lines of sequent calculus. Shengen generates guard types with unexported fields and validated constructors. The LLM writes implementation code against those types. Five gates run in sequence -- shengen sync, tests, build, shen tc+, tcb audit -- and gate failures are injected into the next LLM prompt. The cycle repeats until all gates pass. The LLM&apos;s job, then, is simply to make the code compile and the tests pass. But because the types were generated from a formal spec, making the code compile turns out to be equivalent to satisfying the formal invariants. The compiler is the proof checker; the LLM is, for lack of a better term, the code monkey.&lt;/p&gt;
&lt;p&gt;I think this works better than one might initially expect, for reasons that become clearer when you consider the specific failure modes of each approach. Current models are genuinely excellent at writing code that compiles. They understand type errors. They know how to respond to messages like &quot;cannot use X as type Y&quot; or &quot;unexported field.&quot; This is the easiest kind of feedback for an LLM to act on, because the error message tells it more or less exactly what is wrong. Contrast this with asking an LLM to write a Lean proof: the error messages from proof assistants are notoriously opaque, referring to proof states, tactic failures, and unification problems that even experienced developers struggle with. There is an asymmetry here that I suspect is underappreciated. The information content of a type error, from the perspective of guiding correction, is dramatically higher than the information content of a proof assistant error, even though the proof assistant error is nominally about a &quot;higher-level&quot; concern.&lt;/p&gt;
&lt;p&gt;This connects to a more general observation about the difference between type errors and test failures as feedback mechanisms. When a test fails, the LLM receives something like &quot;Expected 200, got 403.&quot; It then has to figure out why. Maybe the auth is wrong. Maybe the data setup is wrong. Maybe the test itself is wrong. The causal distance between the symptom and the fix can be enormous. When a type check fails, the LLM gets &quot;cannot use string as type TenantAccess.&quot; The fix is structural -- it needs to construct a TenantAccess value, which requires an AuthenticatedPrincipal and a membership proof. The type system guides the LLM through the proof chain without the LLM knowing it is being guided. I find this particularly interesting because it suggests that the feedback hierarchy in AI coding loops is not simply a matter of strictness but of informational density. Not all forms of backpressure are created equal, and the distinction between them illuminates something about the nature of the constraints we are imposing.&lt;/p&gt;
&lt;p&gt;Guard types have a further property that seems well-matched to how LLMs actually write code: they constrain construction, not usage. Once you have a ResourceAccess value, you can pass it around, store it, return it -- no restrictions. The constraint is narrow, requiring you to prove the invariants to create the value, and the freedom is broad, allowing you to use the value however you want. LLMs are good at following patterns once they have the right types, and they are good at fixing constructor errors. They are notably bad at maintaining invisible invariants across large codebases. Guard types make the invariants visible and the violations loud. This seems to me a subtle but important point about the design of constraints for autonomous systems generally: the most effective constraints are those that are narrow at the point of entry and permissive everywhere else, because this matches the error profile of the agents being constrained.&lt;/p&gt;
&lt;p&gt;The implications for AI safety seem worth considering carefully. As AI coding agents become more autonomous -- running in loops, making architectural decisions, committing code -- the question of how to constrain them becomes pressing. The dominant approach is behavioral: train the model to be careful, add review steps, use tests as gates. Shen-Backpressure offers a structural alternative: make the correct behavior the only behavior that compiles. The model does not need to be careful. It does not need to understand the invariants. It needs to produce code that type-checks, and the invariants are enforced by the compiler, not by the model&apos;s judgment. The difference is between something like &quot;please remember to check tenant access,&quot; which is a behavioral constraint that the model might forget, and &quot;this function requires a TenantAccess parameter,&quot; which is a structural constraint where forgetting produces a compile error. I suspect this distinction will become increasingly important as models become more autonomous. Behavioral constraints degrade under autonomy in ways that structural constraints do not, and this asymmetry is not, as far as I can tell, widely appreciated in the current discourse around AI coding safety.&lt;/p&gt;
&lt;p&gt;Every AI coding loop provides some form of backpressure -- error feedback that pushes the LLM toward correct code. But arranging these forms into a hierarchy is instructive:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What It Catches&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Syntax&lt;/td&gt;
&lt;td&gt;Unparseable code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;func main( {&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Structural violations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cannot use string as Amount&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Compile errors&lt;/td&gt;
&lt;td&gt;&lt;code&gt;undefined: NewTenantAccess&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Test&lt;/td&gt;
&lt;td&gt;Behavioral errors&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Expected 200, got 403&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Proof chain&lt;/td&gt;
&lt;td&gt;Missing invariant proofs&lt;/td&gt;
&lt;td&gt;Can&apos;t construct &lt;code&gt;ResourceAccess&lt;/code&gt; without &lt;code&gt;TenantAccess&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Deductive&lt;/td&gt;
&lt;td&gt;Spec inconsistencies&lt;/td&gt;
&lt;td&gt;Contradictory Shen rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most AI coding loops operate at levels zero through three. Shen-Backpressure adds levels four and five. What strikes me about this hierarchy is that the higher levels catch errors that lower levels structurally cannot express -- a proof chain violation is not a syntax error or a test failure, it is a structural impossibility that only the type system can articulate. This is not merely a quantitative improvement in error coverage; it represents a qualitatively different kind of feedback, one that encodes domain-specific invariants into the compilation process itself. The analogy that comes to mind, imperfect as it may be, is the difference between teaching someone the rules of chess and giving them a board where the pieces physically cannot move to illegal squares. Both produce rule-following behavior, but through fundamentally different mechanisms, and with very different failure profiles under stress.&lt;/p&gt;
&lt;p&gt;The Ralph loop for the multi-tenant API received a prompt with four rules: wrap raw values at the boundary using guard type constructors; trust guard types internally without re-validating; follow the proof chain by constructing A before B when B requires A; and extract raw values with accessors for SQL, JSON, and templates. That is all. Four rules, plus a plan with eight implementation items. The LLM did not know about sequent calculus. It did not know the types were generated from a formal spec. It followed the rules, wrote code that compiled, and in doing so produced a formally verified authorization system. The AI was constrained into correctness by the type system -- not taught, not prompted, constrained.&lt;/p&gt;
&lt;p&gt;Martin Kleppmann&apos;s thesis, as I understand it, is that AI will make formal verification go mainstream because LLMs can write proof scripts. I think the counter-thesis is at least as plausible, and perhaps more so: AI makes formal verification go mainstream because LLMs can write code that compiles, and compilation can be made equivalent to proof checking through spec-derived guard types. The distinction maps onto a broader question that I find myself returning to in many contexts -- whether it is more effective to make agents smarter about constraints or to make constraints smarter about agents. In the case of formal verification for AI-generated code, I suspect the latter approach will prove more robust, more scalable, and ultimately more consequential. You do not need smarter models. You need smarter types.&lt;/p&gt;
</content:encoded></item><item><title>From Sequent Calculus to Guard Types: How Shengen Works</title><link>https://reubenbrooks.dev/blog/from-sequent-calculus-to-guard-types-how-shengen-works/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/from-sequent-calculus-to-guard-types-how-shengen-works/</guid><description>Six patterns classify every Shen datatype rule, and each generates different code. A tour of the codegen taxonomy and the hidden structure of the validation problem.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There is something striking about the ratios. The payment processor spec is 48 lines of Shen. The generated Go code is 120 lines. The email campaign spec is 93 lines; its generated code, 380 lines. The multi-tenant API spec runs to about 100 lines and produces 290 lines of Go. In every case, the spec is shorter than the generated code, easier to review, and encodes invariants that would otherwise require hundreds of lines of hand-written validation -- validation that, in my experience, someone will eventually forget to call. I suspect most working programmers have an intuitive sense of this problem. We know that validation logic sprawls, that it becomes inconsistent, that the gap between what we intended and what we enforce widens quietly over time. What interests me about Shengen is that it takes a specific position on where that gap comes from and offers a surprisingly compact mechanism for closing it.&lt;/p&gt;
&lt;p&gt;The core notation is sequent calculus, the same formalism used in programming language theory papers. Premises sit above a horizontal line, the conclusion below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype amount
  X : number;
  (&amp;gt;= X 0) : verified;
  ====================
  X : amount;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reads: if X is a number, and X &amp;gt;= 0 is verified, then X inhabits the type &lt;code&gt;amount&lt;/code&gt;. It is not a type annotation or a schema; it is a proof rule, a statement about what must be true for a value to carry this type. If you have read PL theory papers, you have seen this notation before. If you have not, I think it is more accessible than it looks at first glance, because each block is self-contained and there are only a few recurring patterns. You can learn to read it in an hour, which is more than I can say for most formal specification languages I have encountered.&lt;/p&gt;
&lt;p&gt;What Shengen does with these blocks is classify them. Every &lt;code&gt;datatype&lt;/code&gt; block falls into one of six patterns, and each pattern generates different code. I want to walk through all six, because I think the taxonomy itself reveals something interesting about the hidden structure of the validation problem -- that what we usually treat as a single undifferentiated task (checking that data is good) actually decomposes into qualitatively distinct operations, each with different implications for code generation.&lt;/p&gt;
&lt;p&gt;The first pattern is what I would call a wrapper: no validation, just naming.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype account-id
  X : string;
  ==============
  X : account-id;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates the most minimal Go imaginable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type AccountId struct{ v string }
func NewAccountId(x string) AccountId { return AccountId{v: x} }
func (t AccountId) Val() string { return t.v }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No error return, no validation. This is pure naming -- distinguishing an account ID from any other string at the type level. The compiler then prevents &lt;code&gt;NewTransaction(amount, accountId, tenantId)&lt;/code&gt; when you meant &lt;code&gt;NewTransaction(amount, fromAccountId, toAccountId)&lt;/code&gt;. You would be amazed, or perhaps you would not be, at how many production bugs amount to &quot;passed the right type of string to the wrong parameter.&quot; This pattern addresses a class of error that exists entirely in the semantic gap between what the programmer meant and what the type system can see. It is, in a sense, the simplest possible proof: this string is not just any string, it is this particular kind of string.&lt;/p&gt;
&lt;p&gt;The second pattern introduces runtime validation at the boundary -- what I think of as the constrained type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype amount
  X : number;
  (&amp;gt;= X 0) : verified;
  ====================
  X : amount;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Amount struct{ v float64 }

func NewAmount(x float64) (Amount, error) {
    if !(x &amp;gt;= 0) {
        return Amount{}, fmt.Errorf(&quot;x must be &amp;gt;= 0: %v&quot;, x)
    }
    return Amount{v: x}, nil
}

func (t Amount) Val() float64 { return t.v }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The constructor now returns &lt;code&gt;(Amount, error)&lt;/code&gt;. The validation runs once, at construction time. After that, any function receiving an &lt;code&gt;Amount&lt;/code&gt; knows it is non-negative without checking again. This is the principle sometimes called &quot;parse, don&apos;t validate&quot; -- the parsed representation carries the proof with it. I find this idea compelling in a way that goes beyond mere convenience. There is a hidden premise in the conventional approach to validation that I think deserves scrutiny: the assumption that checking a value&apos;s properties is something you do at the point of use, repeatedly, scattered across a codebase. This seems natural, but it implicitly treats the knowledge that &quot;this number is non-negative&quot; as something that cannot be captured in the type system, that must be re-established each time. The constrained pattern rejects that premise. Multiple constraints compose straightforwardly; the dosage calculator&apos;s &lt;code&gt;patient-weight&lt;/code&gt; type, for instance, requires both &lt;code&gt;(&amp;gt; X 0)&lt;/code&gt; and &lt;code&gt;(&amp;lt;= X 500)&lt;/code&gt;, and the generated constructor checks both.&lt;/p&gt;
&lt;p&gt;The third pattern is the composite, where multiple fields are typed by guard types rather than primitives:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype transaction
  Amount : amount;
  From : account-id;
  To : account-id;
  ===================================
  [Amount From To] : transaction;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Transaction struct {
    amount Amount
    from   AccountId
    to     AccountId
}

func NewTransaction(amount Amount, from AccountId, to AccountId) Transaction {
    return Transaction{amount: amount, from: from, to: to}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The constructor takes guard types, not primitives. You cannot create a &lt;code&gt;Transaction&lt;/code&gt; with a raw &lt;code&gt;float64&lt;/code&gt;; you need an &lt;code&gt;Amount&lt;/code&gt;, which means passing through &lt;code&gt;NewAmount&lt;/code&gt;, which validates non-negativity. The type system chains the proofs. This is where I think the approach starts to exhibit something genuinely interesting from a theoretical standpoint. Each composite type is not merely a container of validated data; it is a node in a proof graph, and the edges of that graph are enforced by the compiler. A composite type is like a document that can only be assembled from notarized components -- the notarization has already occurred and is attested by the type itself.&lt;/p&gt;
&lt;p&gt;The fourth pattern is what Shengen calls guarded -- a composite that also includes validation involving its fields:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype balance-invariant
  Bal : number;
  Tx : transaction;
  (&amp;gt;= Bal (head Tx)) : verified;
  =======================================
  [Bal Tx] : balance-checked;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type BalanceChecked struct {
    bal float64
    tx  Transaction
}

func NewBalanceChecked(bal float64, tx Transaction) (BalanceChecked, error) {
    if !(bal &amp;gt;= tx.Amount().Val()) {
        return BalanceChecked{}, fmt.Errorf(&quot;bal must be &amp;gt;= tx.amount&quot;)
    }
    return BalanceChecked{bal: bal, tx: tx}, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note how &lt;code&gt;(head Tx)&lt;/code&gt; becomes &lt;code&gt;tx.Amount().Val()&lt;/code&gt;. Shengen resolves the Shen accessor chain -- &lt;code&gt;head&lt;/code&gt; of a three-element list means the first element, which maps to the &lt;code&gt;amount&lt;/code&gt; field -- into Go accessor calls. The premise &lt;code&gt;(&amp;gt;= Bal (head Tx))&lt;/code&gt; becomes &lt;code&gt;bal &amp;gt;= tx.Amount().Val()&lt;/code&gt;. This is where the code generation bridge does real work, translating between the mathematical notation and idiomatic Go. I find this translation step particularly interesting because it sits at exactly the boundary where formal specification languages usually break down. The gap between &quot;what the math says&quot; and &quot;what the code does&quot; is precisely where subtle bugs live, and Shengen&apos;s pattern-classification approach gives it a structured way to cross that gap rather than leaving it to ad-hoc interpretation.&lt;/p&gt;
&lt;p&gt;The fifth pattern is perhaps the most powerful: the proof chain, which requires previous proofs as input.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype safe-transfer
  Tx : transaction;
  Check : balance-checked;
  =============================
  [Tx Check] : safe-transfer;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type SafeTransfer struct {
    tx    Transaction
    check BalanceChecked
}

func NewSafeTransfer(tx Transaction, check BalanceChecked) SafeTransfer {
    return SafeTransfer{tx: tx, check: check}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is no validation logic in the constructor, but it requires a &lt;code&gt;BalanceChecked&lt;/code&gt; value, which can only exist if the balance check passed. The proof is transitive: &lt;code&gt;SafeTransfer&lt;/code&gt; implies &lt;code&gt;BalanceChecked&lt;/code&gt;, which implies &lt;code&gt;bal &amp;gt;= tx.amount&lt;/code&gt;, which implies &lt;code&gt;amount &amp;gt;= 0&lt;/code&gt;. Any function accepting a &lt;code&gt;SafeTransfer&lt;/code&gt; gets all four guarantees for free. In the multi-tenant demo, this chain runs five links deep. At the end, a &lt;code&gt;ResourceAccess&lt;/code&gt; type carries proofs of JWT validity, token freshness, authentication, tenant membership, and resource ownership -- and the handler code simply accepts the type. I suspect this is where the real leverage lies. In conventional code, these kinds of transitive guarantees exist only as comments or as implicit contracts maintained by programmer discipline. The proof chain pattern makes them structural, enforced by the compiler, and -- crucially -- visible in the spec.&lt;/p&gt;
&lt;p&gt;The sixth and final pattern handles sum types, where multiple paths lead to the same conclusion type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype human-principal
  Auth : authenticated-user;
  ===========================
  Auth : authenticated-principal;)

(datatype service-principal
  Cred : service-credential;
  ============================
  Cred : authenticated-principal;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two blocks produce the same conclusion type. Shengen generates a Go interface with a private marker method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type AuthenticatedPrincipal interface {
    isAuthenticatedPrincipal()
}

type HumanPrincipal struct { auth AuthenticatedUser }
func (t HumanPrincipal) isAuthenticatedPrincipal() {}

type ServicePrincipal struct { cred ServiceCredential }
func (t ServicePrincipal) isAuthenticatedPrincipal() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The private marker method seals the interface -- no external code can implement it. This gives you sum types in Go: an &lt;code&gt;AuthenticatedPrincipal&lt;/code&gt; is either a &lt;code&gt;HumanPrincipal&lt;/code&gt; or a &lt;code&gt;ServicePrincipal&lt;/code&gt;, and nothing else. Cron jobs and API callers can flow through the same proof chain via different entry points. I think this pattern is worth pausing on because Go famously lacks algebraic data types, and the usual workarounds -- empty interface values, stringly-typed discriminators -- sacrifice precisely the kind of compile-time safety that the rest of the system is trying to establish. The sealed interface approach is a well-known Go idiom, but generating it from a formal spec closes the loop in a way that manual coding does not.&lt;/p&gt;
&lt;p&gt;Having walked through the six patterns, I want to return to the question of the spec-to-code ratio, because I think there is something deeper going on than mere brevity. The payment processor spec is 48 lines; the hand-written equivalent, including tests for each validation, would be roughly 200 lines. The email campaign spec is 93 lines versus perhaps 600 hand-written. The multi-tenant API is 100 lines versus perhaps 500. The dosage calculator, 100 lines versus perhaps 700. The spec is reviewable in a way that the generated code is not, and does not need to be. A domain expert can read 48 lines of sequent calculus and verify that the payment invariants are correct. The generated code is correct by construction, modulo bugs in Shengen itself, a caveat I want to flag honestly because it matters: the trust model shifts from &quot;did every developer correctly implement every validation&quot; to &quot;is the code generator correct.&quot; This is a meaningful trade, not an elimination of risk. But I suspect it is a favorable one, because the generator is a single artifact that can be tested and audited, whereas hand-written validation is distributed across an entire codebase and maintained by an ever-changing cast of developers.&lt;/p&gt;
&lt;p&gt;There is a meta-level to this system that I find intriguing. The &lt;code&gt;create-shengen&lt;/code&gt; command is an 875-line specification that teaches an LLM to build a Shengen for any target language. It specifies the input grammar (Shen &lt;code&gt;datatype&lt;/code&gt; blocks), the classification algorithm, the per-language enforcement mechanism (unexported fields in Go, &lt;code&gt;pub(crate)&lt;/code&gt; in Rust, private constructors in TypeScript, closures in Python), and the code generation patterns for each of the six type categories. This is, in effect, a compiler-generating compiler. One LLM invocation produces a complete Shengen for Rust or Python or C#. The formal verification supply chain then becomes: one human writes a spec, one tool -- potentially LLM-generated -- produces guard types, and the target language&apos;s compiler enforces them. I am not entirely sure what to make of this. On one hand, it is an elegant demonstration of how formal specifications can propagate through layers of tooling. On the other hand, it introduces a dependency on LLM correctness at the tooling layer, which raises questions I do not think have been fully answered yet about how one audits and trusts such a chain. It seems to me that the trust problem does not disappear; it shifts shape.&lt;/p&gt;
&lt;p&gt;Shengen&apos;s internals, roughly 1600 lines of Go, work in four stages. First, parsing: read &lt;code&gt;.shen&lt;/code&gt; files, tokenize, parse &lt;code&gt;(datatype ...)&lt;/code&gt; blocks as S-expression trees. Second, classification: build a symbol table mapping Shen type names to Go names and categories, using two-pass resolution to handle cases where the block name differs from the conclusion type name (for example, block &lt;code&gt;balance-invariant&lt;/code&gt; concludes type &lt;code&gt;balance-checked&lt;/code&gt;). Third, resolution: map &lt;code&gt;verified&lt;/code&gt; premises to Go conditions, translate accessor chains like &lt;code&gt;(head X)&lt;/code&gt;, &lt;code&gt;(tail X)&lt;/code&gt;, &lt;code&gt;(head (tail X))&lt;/code&gt; to Go method calls, and handle structural equality premises where accessor resolution fails. Fourth, emission: generate the Go file with package declaration, imports, and one type block per datatype including struct, constructor, accessors, and a String method. The generated file always carries a &lt;code&gt;DO NOT EDIT&lt;/code&gt; header, and Gate 5 of the audit process verifies that the committed file matches what Shengen would generate. Nobody can hand-edit the guards.&lt;/p&gt;
&lt;p&gt;I want to close with an open question that I have been turning over. The six-pattern taxonomy feels right to me -- it captures the validation structures I encounter in practice -- but I wonder whether it is complete. Is there a seventh pattern that would naturally arise in, say, concurrent systems where proofs need to be invalidated and re-established? What about temporal properties, where a proof holds only for a bounded duration? The token freshness check in the multi-tenant demo gestures in this direction, but it treats time as just another constraint, not as a fundamentally different kind of premise. It seems to me that there might be a meaningful distinction between proofs that hold permanently once established and proofs that decay, and that this distinction could warrant its own pattern. I do not have a clear answer here, but I suspect that the boundary of the six-pattern taxonomy marks the boundary of the class of systems for which this approach is most naturally suited, and that understanding where it breaks down would teach us something about the deeper structure of the validation problem.&lt;/p&gt;
</content:encoded></item><item><title>One Rule, Three Layers: How a Shen Type Becomes Both a Compiler Check and a Runtime Guard</title><link>https://reubenbrooks.dev/blog/one-rule-three-layers-how-shen-types-become-compiler-and-runtime-checks/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/one-rule-three-layers-how-shen-types-become-compiler-and-runtime-checks/</guid><description>A single sequent-calculus rule produces deductive, compile-time, and runtime enforcement — not by bolting layers together, but by exploiting a structural coincidence.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most approaches to correctness give you one thing. Tests give you runtime checks against specific cases. TypeScript gives you compile-time structural constraints. Coq gives you deductive proofs. You pick your tool, you get your layer, and you accept that the others are someone else&apos;s problem.&lt;/p&gt;
&lt;p&gt;What I find genuinely interesting about the Shen-Backpressure approach is that a single declaration, one rule written in Shen&apos;s sequent calculus, produces enforcement at all three layers simultaneously. The deductive layer, the compile-time layer, and the runtime layer all emerge from the same source. This is not because someone built an elaborate framework to connect them. It is because the approach exploits a structural coincidence: the same information that constitutes a proof rule also constitutes a type definition also constitutes a validation check. I want to trace through exactly how this works, show what it looks like across the most interesting domains I have found, and be honest about where the approach has sharp edges.&lt;/p&gt;
&lt;h2&gt;The Three Layers from One Rule&lt;/h2&gt;
&lt;p&gt;Consider a simple rule from a payment system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype amount
  X : number;
  (&amp;gt;= X 0) : verified;
  ====================
  X : amount;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reads: if X is a number, and X &amp;gt;= 0 is verified, then X is an amount. Three things happen to this declaration:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 1: Deductive verification.&lt;/strong&gt; Shen&apos;s own type checker (&lt;code&gt;shen tc+&lt;/code&gt;) verifies that this rule is internally consistent as a sequent calculus judgment. If I write contradictory rules, or rules that cannot compose, Shen catches this before any code is generated. This layer exists in the space of pure logic. It does not know about Go or TypeScript or any target language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2: Compile-time structural enforcement.&lt;/strong&gt; Shengen, the code generator, reads this rule and emits a Go struct with an unexported field:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Amount struct{ v float64 }

func NewAmount(x float64) (Amount, error) {
    if !(x &amp;gt;= 0) {
        return Amount{}, fmt.Errorf(&quot;x must be &amp;gt;= 0: %v&quot;, x)
    }
    return Amount{v: x}, nil
}

func (t Amount) Val() float64 { return t.v }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lowercase &lt;code&gt;v&lt;/code&gt; is unexported. Code outside the &lt;code&gt;shenguard&lt;/code&gt; package cannot write &lt;code&gt;Amount{v: -5}&lt;/code&gt;. The Go compiler rejects it. This is not a runtime check and not a lint warning. It is a hard refusal to produce a binary. The constructor is the only path to the type, and the constructor&apos;s return type &lt;code&gt;(Amount, error)&lt;/code&gt; forces the caller to handle the failure case. The compile-time layer comes from Go&apos;s own type system, recruited into service of the Shen spec.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 3: Runtime validation.&lt;/strong&gt; Inside the constructor, &lt;code&gt;if !(x &amp;gt;= 0)&lt;/code&gt; is a runtime check. When someone calls &lt;code&gt;NewAmount(-5)&lt;/code&gt;, they get an error value back. This is the familiar validation pattern, but it is not hand-written. It is generated from the &lt;code&gt;(&amp;gt;= X 0) : verified&lt;/code&gt; premise in the Shen rule. The runtime check is the same information as the deductive premise, expressed in a different medium.&lt;/p&gt;
&lt;p&gt;One rule. Three enforcement mechanisms. No other approach I am aware of gives you all three from a single declaration. Coq and Lean give you layer 1 but not layers 2-3 in mainstream languages. Tests give you layer 3 but not layers 1-2. TypeScript branded types give you a weak version of layer 2 but not layers 1 or 3. The structural coincidence that makes this possible is that Shen&apos;s sequent-calculus rules contain exactly the information needed for all three: the premises define the type shape (layer 2), the side conditions define the validation logic (layer 3), and the rule structure defines the proof (layer 1).&lt;/p&gt;
&lt;h2&gt;Where It Gets Interesting: Composition&lt;/h2&gt;
&lt;p&gt;Simple wrappers like &lt;code&gt;amount&lt;/code&gt; are useful but not especially surprising. The approach becomes genuinely powerful when rules compose, because composition creates enforcement that is purely structural, with no runtime check at all.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype safe-transfer
  Tx : transaction;
  Check : balance-checked;
  =============================
  [Tx Check] : safe-transfer;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This rule has no &lt;code&gt;verified&lt;/code&gt; premises. There is no arithmetic check, no string comparison, no validation logic whatsoever. The generated constructor is infallible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func NewSafeTransfer(tx Transaction, check BalanceChecked) SafeTransfer {
    return SafeTransfer{tx: tx, check: check}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yet this type is profoundly safe. You cannot construct a &lt;code&gt;SafeTransfer&lt;/code&gt; without a &lt;code&gt;BalanceChecked&lt;/code&gt;, and you cannot construct a &lt;code&gt;BalanceChecked&lt;/code&gt; without proving &lt;code&gt;bal &amp;gt;= tx.amount&lt;/code&gt;. The safety is entirely compile-time. The runtime checks happened earlier in the chain, at the leaf types. The composition is free, both in performance and in enforcement effort.&lt;/p&gt;
&lt;p&gt;This is the pattern that I think deserves the most attention: proof chains where the interior nodes carry no runtime cost. The runtime validation happens once, at construction of the leaf types. Everything above that is pure structural enforcement by the compiler. You get the safety of dependent types with the runtime cost of a few comparisons at the boundary.&lt;/p&gt;
&lt;h2&gt;The Demo Landscape&lt;/h2&gt;
&lt;p&gt;I have been building demos to stress-test this approach across different domains, and the results are instructive because different domains exercise different aspects of the enforcement mechanism. Some of these are fully implemented. Some are specifications waiting for implementations. I will walk through the most interesting ones and flag what still needs to be built.&lt;/p&gt;
&lt;h3&gt;The Grounded Research Pipeline&lt;/h3&gt;
&lt;p&gt;The shen-web-tools demo builds a research pipeline where an AI searches the web, fetches pages, and generates summaries. The key invariant is grounding: the AI cannot cite a source it did not actually retrieve.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype grounded-source
  Page : fetched-page;
  Hit : search-hit;
  (= (head Page) (head (tail Hit))) : verified;
  ===============================================
  [Page Hit] : grounded-source;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;(= (head Page) (head (tail Hit)))&lt;/code&gt; premise is doing something subtle. &lt;code&gt;head Page&lt;/code&gt; extracts the URL from a fetched page (which is &lt;code&gt;[Url Content Timestamp]&lt;/code&gt;). &lt;code&gt;head (tail Hit)&lt;/code&gt; extracts the URL from a search hit (which is &lt;code&gt;[Title Url Snippet]&lt;/code&gt;). The verified premise asserts these are the same URL. You cannot construct a &lt;code&gt;grounded-source&lt;/code&gt; by pairing a fetched page with an unrelated search hit. The URL match is checked at runtime in the constructor. The structural requirement that you &lt;em&gt;have&lt;/em&gt; a &lt;code&gt;grounded-source&lt;/code&gt; before you can produce a &lt;code&gt;research-summary&lt;/code&gt; is checked at compile time.&lt;/p&gt;
&lt;p&gt;This is the pattern in its purest form: the runtime check (URL equality) happens at one specific point, and then the compile-time guarantee (cannot produce ungrounded output) propagates through the rest of the system for free.&lt;/p&gt;
&lt;p&gt;The same demo includes a pipeline state machine with five stages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype pipeline-idle     ...)
(datatype pipeline-searching ...)
(datatype pipeline-fetching  ...)
(datatype pipeline-generating ...)
(datatype pipeline-complete  ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each stage type carries the outputs of its predecessor. You cannot skip from &lt;code&gt;idle&lt;/code&gt; to &lt;code&gt;generating&lt;/code&gt; because &lt;code&gt;pipeline-generating&lt;/code&gt; requires a &lt;code&gt;(list fetched-page)&lt;/code&gt; that can only come from &lt;code&gt;pipeline-fetching&lt;/code&gt;. The state machine is enforced by the type system. There is no enum with a match statement. There is no &quot;invalid state transition&quot; runtime error. The invalid transition is a type error.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build a minimal standalone demo of the pipeline state machine.
Show a simplified 3-stage pipeline (search -&amp;gt; fetch -&amp;gt; summarize) in Go where:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each stage is a separate type carrying the previous stage&apos;s output&lt;/li&gt;
&lt;li&gt;Attempting to skip a stage is a compile error (show the error message)&lt;/li&gt;
&lt;li&gt;The correct path compiles and runs
This should be a self-contained main.go + specs/core.shen + generated shenguard/ that someone can clone and run.
Target: ~50 lines of spec, ~100 lines of application code, clear compile error demonstration. --&amp;gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;The Dosage Calculator: Recursive Type-Level Computation&lt;/h3&gt;
&lt;p&gt;Most Shen type rules use simple arithmetic: &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;. The dosage calculator demo pushes further. Its &lt;code&gt;interaction-clearance&lt;/code&gt; type invokes Shen functions defined within the spec itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(define pair-in-list?
  _ _ [] -&amp;gt; false
  D1 D2 [[D1 D2] | _] -&amp;gt; true
  D1 D2 [[D2 D1] | _] -&amp;gt; true
  D1 D2 [_ | Rest] -&amp;gt; (pair-in-list? D1 D2 Rest))

(define drug-clear-of-list?
  _ [] _ -&amp;gt; true
  Drug [Med | Rest] Pairs -&amp;gt;
    (and (not (pair-in-list? Drug Med Pairs))
         (drug-clear-of-list? Drug Rest Pairs)))

(datatype interaction-clearance
  Drug : drug-name;
  Meds : (list drug-name);
  Pairs : (list (list drug-name));
  (drug-clear-of-list? Drug Meds Pairs) : verified;
  ====================================================
  [Drug Meds Pairs] : interaction-clearance;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is striking because the type-level proof includes recursive list-walking. It is not just &quot;is X &amp;gt;= 0?&quot; It is &quot;walk the entire contraindication list and verify that no forbidden pair exists.&quot; Shen&apos;s type checker, which is itself a Prolog program, executes this computation as part of type checking. The generated Go constructor translates the recursive walk into an iterative helper function.&lt;/p&gt;
&lt;p&gt;The final &lt;code&gt;safe-administration&lt;/code&gt; type requires both &lt;code&gt;dose-in-range&lt;/code&gt; (the dosage falls within the therapeutic range for this patient&apos;s weight) and &lt;code&gt;interaction-clearance&lt;/code&gt; (no contraindicated drug interactions). Both proofs must exist before you can administer. The composition is compile-time. The individual checks are runtime.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build the dosage calculator as a complete working demo.
The spec exists at demo/dosage-calculator/specs/core.shen. What&apos;s needed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run shengen to generate the guard types (guards_gen.go)&lt;/li&gt;
&lt;li&gt;Build out cmd/server/main.go with HTTP handlers for:
&lt;ul&gt;
&lt;li&gt;POST /check-dose: accepts {drug, weight_kg, dose_mg, current_medications}&lt;/li&gt;
&lt;li&gt;Returns either a safe-administration proof summary or a structured error&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Write tests demonstrating:
&lt;ul&gt;
&lt;li&gt;Valid administration (correct dose, no interactions) succeeds&lt;/li&gt;
&lt;li&gt;Overdose attempt fails at dose-in-range construction&lt;/li&gt;
&lt;li&gt;Drug interaction fails at interaction-clearance construction&lt;/li&gt;
&lt;li&gt;Underweight patient (weight &amp;lt;= 0) fails at patient-weight construction&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The demo should be runnable with &lt;code&gt;go run ./cmd/server&lt;/code&gt; and testable with &lt;code&gt;go test ./...&lt;/code&gt;
Key insight to highlight: the recursive drug-clear-of-list? function in Shen becomes a Go helper function. Show both side by side. --&amp;gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Multi-Tenant Authorization: The Proof Chain as Security Architecture&lt;/h3&gt;
&lt;p&gt;The multi-tenant demo has the longest proof chain: seven types, each requiring its predecessor. I have written about this one in detail elsewhere, but the key observation for this piece is how different links in the chain exercise different enforcement mechanisms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JwtToken&lt;/code&gt; and &lt;code&gt;TokenExpiry&lt;/code&gt; are &lt;strong&gt;constrained wrappers&lt;/strong&gt; (runtime validation of non-empty string, timestamp comparison)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AuthenticatedUser&lt;/code&gt; is a &lt;strong&gt;pure composite&lt;/strong&gt; (no runtime check, purely structural, requires the two proofs above)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AuthenticatedPrincipal&lt;/code&gt; is a &lt;strong&gt;sum type&lt;/strong&gt; (Go interface with private marker method, compile-time closed set)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TenantAccess&lt;/code&gt; and &lt;code&gt;ResourceAccess&lt;/code&gt; are &lt;strong&gt;guarded composites&lt;/strong&gt; (runtime boolean check + structural requirement for the proofs above)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A single proof chain exercises four of the six shengen categories. The runtime checks happen at the leaves and at the boolean gates. The structural composition in between is free. The sum type is enforced by Go&apos;s interface mechanism. This is the demo that best illustrates how the three layers interact in a real system.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Create a &quot;proof chain anatomy&quot; diagram/document.
For the multi-tenant demo, trace through a single request and annotate each step:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JWT string arrives as raw input&lt;/li&gt;
&lt;li&gt;NewJwtToken() — RUNTIME CHECK (non-empty string)&lt;/li&gt;
&lt;li&gt;NewTokenExpiry() — RUNTIME CHECK (exp &amp;gt; now)&lt;/li&gt;
&lt;li&gt;NewAuthenticatedUser() — NO RUNTIME CHECK (pure composition)&lt;/li&gt;
&lt;li&gt;NewHumanPrincipal() — NO RUNTIME CHECK (sum type variant construction)&lt;/li&gt;
&lt;li&gt;NewTenantAccess() — RUNTIME CHECK (isMember == true)&lt;/li&gt;
&lt;li&gt;NewResourceAccess() — RUNTIME CHECK (isOwned == true)&lt;/li&gt;
&lt;li&gt;Handler receives ResourceAccess — COMPILE-TIME ONLY (type signature)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Show the Go code at each step, mark which layer is doing the enforcement,
and calculate: out of 7 construction steps, only 4 have runtime cost.
The other 3 are pure compile-time structural enforcement.&lt;/p&gt;
&lt;p&gt;This should be a code walkthrough with annotations, not prose.
Format as a markdown document with code blocks and callout boxes. --&amp;gt;&lt;/p&gt;
&lt;h3&gt;Email Campaigns: Relational Invariants Across Types&lt;/h3&gt;
&lt;p&gt;The email demo has the most unusual arithmetic constraint I have encountered in any of the specs: &lt;code&gt;(= 0 (shen.mod X 10))&lt;/code&gt; on &lt;code&gt;age-decade&lt;/code&gt;. This does not just require a number in a range. It requires the number to be a valid decade (10, 20, 30, ..., 100). The modular arithmetic is checked at runtime in the constructor.&lt;/p&gt;
&lt;p&gt;But the more interesting invariant is relational. The &lt;code&gt;copy-delivery&lt;/code&gt; type asserts that the demographics embedded in a user profile match the demographics the email copy was written for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype copy-delivery
  Profile : known-profile;
  Copy : copy-content;
  (= (tail (tail (head Profile))) (tail Copy)) : verified;
  =========================================================
  [Profile Copy] : copy-delivery;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This navigates the internal structure of two different composite types via &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt;, extracts specific fields, and asserts equality between them. It is the only demo where a verified premise compares subfields of two different composite types rather than comparing a field to a constant or to another field of the same type. This matters because it shows the approach can enforce relational constraints, properties that span multiple objects, not just local constraints on a single object.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build a standalone demo of a relational cross-type constraint.
Use the email_crud spec as inspiration but simplify to the core pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype region-config
  Region : string;
  Currency : string;
  TaxRate : number;
  =============================
  [Region Currency TaxRate] : region-config;)

(datatype order-pricing
  Config : region-config;
  Price : number;
  (= (head (tail Config)) Currency) : verified;  \\ currency must match
  (&amp;gt;= Price 0) : verified;
  ============================================
  [Config Price Currency] : order-pricing;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The demo should show:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Constructing a region-config for &quot;US&quot; with &quot;USD&quot; and 0.08 tax rate&lt;/li&gt;
&lt;li&gt;Constructing an order-pricing that matches the region&apos;s currency (succeeds)&lt;/li&gt;
&lt;li&gt;Attempting to construct an order-pricing with a mismatched currency (fails)&lt;/li&gt;
&lt;li&gt;Show how the generated Go code translates the &lt;code&gt;head (tail Config)&lt;/code&gt; accessor chain&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This illustrates relational invariants in ~20 lines of spec. --&amp;gt;&lt;/p&gt;
&lt;h3&gt;Closed Enumerations: Rejecting LLM Hallucination&lt;/h3&gt;
&lt;p&gt;The Medicare subdomain of shen-web-tools contains what I think is the most directly practical pattern for AI-assisted development: closed enumerations.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype medicare-plan-type
  X : string;
  (element? X [&quot;original&quot; &quot;advantage&quot; &quot;part-d&quot; &quot;supplement&quot; &quot;part-a&quot; &quot;part-b&quot;]) : verified;
  ========================================================================================
  X : medicare-plan-type;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;element?&lt;/code&gt; check compiles to &lt;code&gt;map[string]bool{...}[val]&lt;/code&gt; in Go, &lt;code&gt;new Set([...]).has(val)&lt;/code&gt; in TypeScript, &lt;code&gt;[...].contains(&amp;amp;val)&lt;/code&gt; in Rust. If an LLM generates a plan type of &lt;code&gt;&quot;premium&quot;&lt;/code&gt; or &lt;code&gt;&quot;gold tier&quot;&lt;/code&gt; or any other hallucinated string, the constructor rejects it. The closed set is enforced at runtime, and the opaque type prevents bypass at compile time.&lt;/p&gt;
&lt;p&gt;The same spec has a &lt;code&gt;panel-kind&lt;/code&gt; enumeration with 15 valid values for dashboard panel types. When an LLM generates a UI layout, every panel must have a valid kind. Hallucinated panel types are rejected at the guard boundary. This is backpressure applied directly to LLM output quality.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build a demo of closed enumeration as LLM hallucination prevention.
Scenario: An LLM generates structured JSON describing a dashboard layout.
Each panel has a &quot;kind&quot; field. The spec defines exactly which kinds are valid.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype panel-kind
  X : string;
  (element? X [&quot;bar-chart&quot; &quot;line-chart&quot; &quot;pie-chart&quot; &quot;table&quot; &quot;metric-card&quot;
               &quot;scatter-plot&quot; &quot;heatmap&quot; &quot;timeline&quot; &quot;map&quot; &quot;text-block&quot;]) : verified;
  =================================================================================
  X : panel-kind;)

(datatype dashboard-panel
  Kind : panel-kind;
  Title : string;
  DataSource : string;
  (not (= Title &quot;&quot;)) : verified;
  (not (= DataSource &quot;&quot;)) : verified;
  ====================================
  [Kind Title DataSource] : dashboard-panel;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build a Go service that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Accepts JSON from an LLM (simulated) describing a dashboard layout&lt;/li&gt;
&lt;li&gt;Validates each panel through the guard types&lt;/li&gt;
&lt;li&gt;Demonstrates: valid panels pass, hallucinated panel kinds are rejected,
empty titles are rejected&lt;/li&gt;
&lt;li&gt;Show the error messages that would be fed back to the LLM in a Ralph loop&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the &quot;backpressure on LLM output&quot; story made concrete.
Target: ~30 lines of spec, ~80 lines of Go, clear before/after of valid vs invalid LLM output. --&amp;gt;&lt;/p&gt;
&lt;h3&gt;Order State Machine: Deadlock Freedom as a Type Error&lt;/h3&gt;
&lt;p&gt;This is the demo I am most excited about that does not yet exist. The concept: encode a state machine&apos;s valid transitions in the Shen type system such that invalid transitions are compile errors and deadlock freedom (every non-terminal state has at least one outward transition) is a property of the spec itself.&lt;/p&gt;
&lt;p&gt;The idea is that each state is a type, and each transition is a function whose type signature encodes the source and destination states:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype order-created   ...)
(datatype order-paid      ...)
(datatype order-shipped   ...)
(datatype order-delivered  ...)  \\ terminal
(datatype order-cancelled  ...)  \\ terminal

(datatype transition-pay
  Order : order-created;
  Payment : payment-info;
  (&amp;gt; (head Payment) 0) : verified;
  =================================
  [Order Payment] : order-paid;)

(datatype transition-ship
  Order : order-paid;
  Tracking : tracking-number;
  ============================
  [Order Tracking] : order-shipped;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;transition-pay&lt;/code&gt; function requires an &lt;code&gt;order-created&lt;/code&gt; and produces an &lt;code&gt;order-paid&lt;/code&gt;. You cannot ship an order that has not been paid because &lt;code&gt;transition-ship&lt;/code&gt; requires &lt;code&gt;order-paid&lt;/code&gt;, not &lt;code&gt;order-created&lt;/code&gt;. The state machine is the type system. Invalid transitions are not runtime errors; they are compile errors.&lt;/p&gt;
&lt;p&gt;Deadlock freedom comes from the spec structure: if every non-terminal state appears as a premise in at least one transition rule, then every non-terminal state has at least one valid outward transition. Shen&apos;s type checker can verify this property.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build the order state machine demo from scratch.
This is the most ambitious demo and does not yet exist.&lt;/p&gt;
&lt;p&gt;Phase 1 — The spec (specs/core.shen):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5 states: created, paid, processing, shipped, delivered (terminal), cancelled (terminal), refund-requested, refunded (terminal)&lt;/li&gt;
&lt;li&gt;Valid transitions encoded as datatype rules where the source state is a premise and the destination state is the conclusion&lt;/li&gt;
&lt;li&gt;Each transition may carry additional data (payment info, tracking number, refund reason)&lt;/li&gt;
&lt;li&gt;Some transitions have guards (payment amount &amp;gt; 0, tracking number non-empty)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Phase 2 — Generated guard types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run shengen to generate the Go types&lt;/li&gt;
&lt;li&gt;Each state is a struct with unexported fields&lt;/li&gt;
&lt;li&gt;Each transition is a constructor function: NewOrderPaid(created OrderCreated, payment PaymentInfo) (OrderPaid, error)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Phase 3 — Application code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A simple order management API with handlers for each transition&lt;/li&gt;
&lt;li&gt;GET /order/:id returns current state&lt;/li&gt;
&lt;li&gt;POST /order/:id/pay, /ship, /cancel, /refund-request, /refund&lt;/li&gt;
&lt;li&gt;Each handler takes the current state type and attempts the transition&lt;/li&gt;
&lt;li&gt;Invalid transitions are compile errors (the handler literally cannot accept the wrong state type)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Phase 4 — The deadlock freedom test:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write a test or verification script that checks: for every non-terminal state type,
there exists at least one transition rule that accepts it as a premise&lt;/li&gt;
&lt;li&gt;This is a static property of the spec, verifiable by Shen or by inspecting the AST&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Phase 5 — The compelling demonstration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Show an LLM attempting to add a &quot;ship from created&quot; shortcut&lt;/li&gt;
&lt;li&gt;The code won&apos;t compile because transition-ship requires order-paid&lt;/li&gt;
&lt;li&gt;Show the compiler error message&lt;/li&gt;
&lt;li&gt;Show the LLM correcting itself by adding the payment step&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This should be a full working demo in demo/order-state-machine/.
It is the strongest demonstration of &quot;impossible by construction&quot; applied to business logic. --&amp;gt;&lt;/p&gt;
&lt;h3&gt;Sum Types: Multiple Valid Paths to the Same Proof&lt;/h3&gt;
&lt;p&gt;The multi-tenant demo uses sum types for &lt;code&gt;AuthenticatedPrincipal&lt;/code&gt;: both &lt;code&gt;HumanPrincipal&lt;/code&gt; and &lt;code&gt;ServicePrincipal&lt;/code&gt; produce the same interface type. In Go, this becomes an interface with a private marker method. In Rust, a sealed trait. In TypeScript, a union type.&lt;/p&gt;
&lt;p&gt;What makes this interesting for the three-layer story is that the sum type adds a &lt;em&gt;fourth&lt;/em&gt; enforcement mechanism: the closed variant set. In Go, the private marker method &lt;code&gt;isAuthenticatedPrincipal()&lt;/code&gt; means external packages cannot add new variants. The compiler enforces the closed set at compile time. You cannot introduce a &lt;code&gt;BotPrincipal&lt;/code&gt; without modifying the &lt;code&gt;shenguard&lt;/code&gt; package, which is generated and audited by Gate 5.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build a standalone demo of sum types with closed variant enforcement.
Use a simpler domain than multi-tenant to isolate the pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype shape-circle
  Radius : number;
  (&amp;gt; Radius 0) : verified;
  =========================
  Radius : shape;)

(datatype shape-rectangle
  Width : number;
  Height : number;
  (&amp;gt; Width 0) : verified;
  (&amp;gt; Height 0) : verified;
  ==========================
  [Width Height] : shape;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generate guard types in Go showing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Shape interface with private marker method&lt;/li&gt;
&lt;li&gt;Circle and Rectangle as the only implementors&lt;/li&gt;
&lt;li&gt;A function &lt;code&gt;area(s Shape) float64&lt;/code&gt; that uses a type switch&lt;/li&gt;
&lt;li&gt;Attempting to add a new variant outside the package fails to compile&lt;/li&gt;
&lt;li&gt;Show the compile error message&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then generate the same spec in TypeScript showing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;type Shape = Circle | Rectangle&lt;/code&gt; union&lt;/li&gt;
&lt;li&gt;Exhaustive pattern matching with never-type check&lt;/li&gt;
&lt;li&gt;Adding a new variant causes a TS compile error in the match&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This demonstrates the same Shen sum-type rule enforced via different
language mechanisms. --&amp;gt;&lt;/p&gt;
&lt;h2&gt;The Category System: Six Patterns, Six Enforcement Profiles&lt;/h2&gt;
&lt;p&gt;Shengen classifies every Shen datatype rule into one of six categories, and each category has a distinct enforcement profile. Understanding these categories is the key to understanding what you get from a given rule.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Shen Shape&lt;/th&gt;
&lt;th&gt;Compile-Time&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;wrapper&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single premise, primitive type, no guards&lt;/td&gt;
&lt;td&gt;Opaque field, forced constructor&lt;/td&gt;
&lt;td&gt;None (infallible)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;account-id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;constrained&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single premise, primitive type, with guards&lt;/td&gt;
&lt;td&gt;Opaque field, forced constructor, error return&lt;/td&gt;
&lt;td&gt;Validated in constructor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;email-address&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;alias&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single premise, non-primitive type, no guards&lt;/td&gt;
&lt;td&gt;Type synonym (transparent)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prompt-required = unknown-profile&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;composite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bracketed conclusion, no guards&lt;/td&gt;
&lt;td&gt;Opaque fields, forced constructor&lt;/td&gt;
&lt;td&gt;None (infallible)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;transaction&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;guarded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bracketed conclusion, with guards&lt;/td&gt;
&lt;td&gt;Opaque fields, forced constructor, error return&lt;/td&gt;
&lt;td&gt;Validated in constructor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;balance-checked&lt;/code&gt;, &lt;code&gt;tenant-access&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;sumtype&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple blocks, same conclusion&lt;/td&gt;
&lt;td&gt;Closed interface/trait/union&lt;/td&gt;
&lt;td&gt;Per-variant (varies)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;authenticated-principal&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The pattern I want to highlight: &lt;strong&gt;composite&lt;/strong&gt; types have zero runtime cost. They are pure structural enforcement. This means that in a proof chain, the intermediate composition steps are free. Only the leaf types (constrained, guarded) carry runtime validation cost. The deeper your proof chain, the better the ratio of compile-time to runtime enforcement.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build a &quot;category showcase&quot; that demonstrates all six categories in one spec.
Design a small domain (perhaps a document management system) where one spec file
exercises all six shengen categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;wrapper: DocumentId (string wrapper, no validation)&lt;/li&gt;
&lt;li&gt;constrained: PageCount (number, must be &amp;gt; 0)&lt;/li&gt;
&lt;li&gt;alias: DraftDocument = Document (type synonym)&lt;/li&gt;
&lt;li&gt;composite: Document [DocumentId Title PageCount] (no cross-field guards)&lt;/li&gt;
&lt;li&gt;guarded: PublishedDocument [Document ReviewerSignoff] where (= Approved true) : verified&lt;/li&gt;
&lt;li&gt;sumtype: AccessLevel = ReadOnly | ReadWrite (two blocks, same conclusion)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Generate Go guard types and write a short program that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates each type, showing the constructor signature&lt;/li&gt;
&lt;li&gt;Demonstrates which constructors return errors (constrained, guarded) vs which are infallible (wrapper, composite)&lt;/li&gt;
&lt;li&gt;Shows the compile error when trying to bypass the opaque field&lt;/li&gt;
&lt;li&gt;Annotates each type with its category and enforcement profile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the &quot;Rosetta Stone&quot; for understanding shengen output.
Target: ~40 lines of spec, ~60 lines of generated Go, ~50 lines of demonstration code. --&amp;gt;&lt;/p&gt;
&lt;h2&gt;The Killer Insight: Proof Chains Have Diminishing Runtime Cost&lt;/h2&gt;
&lt;p&gt;Here is what I think is the most underappreciated aspect of this approach. Consider a proof chain of depth N:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;leaf-type-1 (runtime check)
  └─&amp;gt; intermediate-1 (compile-time only)
       └─&amp;gt; intermediate-2 (compile-time only)
            └─&amp;gt; ... (compile-time only)
                 └─&amp;gt; final-proof (compile-time only)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The runtime cost is O(leaves), not O(depth). Every intermediate composition step is a &lt;code&gt;composite&lt;/code&gt; with an infallible constructor. The compiler enforces the chain; the runtime validates the inputs. As your invariants get more sophisticated and your proof chains get deeper, the &lt;em&gt;proportion&lt;/em&gt; of enforcement that is compile-time increases. You pay for validation at the boundary and get structural guarantees throughout the interior for free.&lt;/p&gt;
&lt;p&gt;This is the opposite of how runtime validation typically scales. In a conventional system, deeper validation logic means more runtime checks, more error handling, more test cases. Here, deeper proof chains mean more compile-time enforcement and proportionally less runtime work. The formal properties scale by recruiting the compiler, not by adding code.&lt;/p&gt;
&lt;h2&gt;What Still Needs to Be Built&lt;/h2&gt;
&lt;p&gt;I want to be explicit about the gap between what exists as working code and what exists only as specifications or concepts, because I think intellectual honesty about the state of a project is more valuable than presenting everything as equally complete.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fully implemented and working:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment processor (Go) - simple but complete proof chain&lt;/li&gt;
&lt;li&gt;Multi-tenant API (Go) - full seven-type chain, tests, middleware, admin dashboard&lt;/li&gt;
&lt;li&gt;Email campaigns (Go) - relational cross-type constraints, working handlers and templates&lt;/li&gt;
&lt;li&gt;Research pipeline (Common Lisp/Shen) - the most architecturally complete, runs on SBCL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Spec complete, implementation needed:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dosage calculator - the spec with recursive Shen functions exists, the Go server is a stub&lt;/li&gt;
&lt;li&gt;Order state machine - only a concept document, the most ambitious planned demo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Concept only:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shen Prolog as active constraint solver for generative UI&lt;/li&gt;
&lt;li&gt;Linear logic / graded modalities for provable concurrency in Go&lt;/li&gt;
&lt;li&gt;Polyglot comparison: same spec, four frameworks (Hono, Axum, FastAPI, net/http)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Build the polyglot comparison demo.
Take the payment spec (simplest complete spec) and generate guard types in all four languages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go (shengen) — unexported fields, (T, error) return&lt;/li&gt;
&lt;li&gt;TypeScript (shengen-ts) — private constructor, static create(), throws&lt;/li&gt;
&lt;li&gt;Rust (shengen-rs) — private fields, Result&amp;lt;Self, GuardError&amp;gt;&lt;/li&gt;
&lt;li&gt;Python standard (shengen-py) — frozen dataclass, &lt;strong&gt;post_init&lt;/strong&gt; raises&lt;/li&gt;
&lt;li&gt;Python hardened (shengen-py --mode hardened) — HMAC provenance chain&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For each, write a small program that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a valid Amount (succeeds)&lt;/li&gt;
&lt;li&gt;Attempts to create an invalid Amount (fails, show the error)&lt;/li&gt;
&lt;li&gt;Attempts to bypass the constructor (show the compile/runtime error)&lt;/li&gt;
&lt;li&gt;Creates a full safe-transfer proof chain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Put all five side by side in a single document or demo directory.
This is the &quot;one spec, five guarantees&quot; story made concrete and runnable.
It directly complements the &quot;enforcement spectrum&quot; blog post. --&amp;gt;&lt;/p&gt;
&lt;h2&gt;The Deeper Question&lt;/h2&gt;
&lt;p&gt;I have been circling around a question that I think is more important than any specific demo: why does this work? Why can a single declaration in a proof calculus produce enforcement at three different layers of the software stack?&lt;/p&gt;
&lt;p&gt;I think the answer has to do with the structure of sequent calculus itself. A sequent calculus rule is simultaneously:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;logical judgment&lt;/strong&gt; (the premises entail the conclusion)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;type definition&lt;/strong&gt; (the premises are the fields, the conclusion is the type)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;validation specification&lt;/strong&gt; (the side conditions are the checks)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These are not three different interpretations bolted together by clever engineering. They are three facets of the same mathematical object. The Curry-Howard correspondence tells us that proofs are programs and types are propositions. Shen&apos;s sequent calculus sits at the nexus where this correspondence becomes practically useful: the proof is the type is the validation. Shengen just mechanically separates the facets into their target-language representations.&lt;/p&gt;
&lt;p&gt;This is why I think the approach has legs beyond the specific demos I have built. Any domain where you can express invariants as sequent calculus rules gets all three layers of enforcement automatically. The question is not &quot;can we make this work for domain X&quot; but &quot;can we express domain X&apos;s invariants in this notation?&quot; And Shen&apos;s type system, being Turing-complete and embedding Prolog, turns out to be more expressive than you might expect from a system that looks, at first glance, like simple type declarations.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- EXAMPLE PROMPT: Write a &quot;how to think in sequent calculus&quot; tutorial.
Target audience: developers who are comfortable with TypeScript/Go type systems
but have never seen formal logic notation.&lt;/p&gt;
&lt;p&gt;Cover:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The basic anatomy: premises above the line, conclusion below&lt;/li&gt;
&lt;li&gt;How to read (&amp;gt;= X 0) : verified as a side condition&lt;/li&gt;
&lt;li&gt;How composition works: using one type as a premise in another rule&lt;/li&gt;
&lt;li&gt;The six shengen categories and how to recognize which one you&apos;re writing&lt;/li&gt;
&lt;li&gt;Common patterns:
&lt;ul&gt;
&lt;li&gt;Wrapping a primitive to give it a name&lt;/li&gt;
&lt;li&gt;Adding a constraint to a primitive&lt;/li&gt;
&lt;li&gt;Composing multiple proofs into a product&lt;/li&gt;
&lt;li&gt;Adding cross-field guards to a product&lt;/li&gt;
&lt;li&gt;Creating sum types (multiple rules, same conclusion)&lt;/li&gt;
&lt;li&gt;Using Shen functions as verification conditions (advanced)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Three worked examples from simple to complex:
&lt;ul&gt;
&lt;li&gt;A validated email address (constrained wrapper)&lt;/li&gt;
&lt;li&gt;A date range where start &amp;lt; end (guarded composite)&lt;/li&gt;
&lt;li&gt;A safe database query that requires both auth and tenant proofs (proof chain)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This could be a blog post or a section of documentation.
It is the missing onboarding material for the entire project. --&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>The Abstraction Ladder Has Always Been There</title><link>https://reubenbrooks.dev/blog/the-abstraction-ladder-has-always-been-there/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/the-abstraction-ladder-has-always-been-there/</guid><description>A spec isn&apos;t an alternative to code — it&apos;s code at a different level of abstraction. The decisions you don&apos;t make get made for you.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There&apos;s a debate happening right now about whether writing formal specifications for AI agents is &quot;real programming&quot; or some kind of ceremonial overhead. The framing is wrong. A spec isn&apos;t an alternative to code — it&apos;s code at a different level of abstraction. And the choice of which level to work at has been the central question of software engineering since 1954.&lt;/p&gt;
&lt;h2&gt;What abstraction actually is&lt;/h2&gt;
&lt;p&gt;Every layer of a programming stack is a contract with the same shape: I will state explicitly the things I care about. I will let the layer below decide everything else.&lt;/p&gt;
&lt;p&gt;When you write C instead of assembly, you&apos;ve decided you care about control flow and memory layout but not register allocation. The compiler picks registers. When you write Python instead of C, you&apos;ve decided you care about logic but not memory management. The runtime picks allocations. When you write SQL instead of a loop, you&apos;ve decided you care about the shape of the result but not the join algorithm. The query planner picks.&lt;/p&gt;
&lt;p&gt;At every step, two things are true simultaneously. You are doing less — fewer decisions, less code, less to get wrong. And you are doing more — more precisely naming the thing you actually want, because the layer below now needs a clear contract to fulfill.&lt;/p&gt;
&lt;p&gt;This is the deal abstraction has always offered. It doesn&apos;t remove the work of knowing what you want. It just changes which parts of &quot;what you want&quot; you have to say out loud.&lt;/p&gt;
&lt;h2&gt;Prompts and specs are rungs on the same ladder&lt;/h2&gt;
&lt;p&gt;When someone writes a prompt like &quot;build me a payment processor that handles refunds,&quot; they are programming. They&apos;re working at a very high level of abstraction — higher than Python, higher than SQL — where the contract is expressed in English and the layer below (the LLM) fills in essentially everything: language, architecture, data model, error handling, invariants.&lt;/p&gt;
&lt;p&gt;When the same person writes a Shen type rule saying &lt;code&gt;balance&lt;/code&gt; must be ≥ &lt;code&gt;transaction amount&lt;/code&gt; before the transfer is marked verified, they&apos;re also programming. Same ladder. Different rung. This time the contract is expressed in sequent calculus, and the layer below (the LLM plus the type checker plus the compiler) fills in everything except that invariant.&lt;/p&gt;
&lt;p&gt;Neither is more or less &quot;real&quot; than the other. Both require the same underlying skill: knowing what you want precisely enough to say it. The English prompt and the formal spec are two points on a continuum that also includes Python, C, and assembly. The only real question is which decisions you want to make explicitly and which you&apos;re willing to delegate.&lt;/p&gt;
&lt;h2&gt;The decisions you don&apos;t make get made for you&lt;/h2&gt;
&lt;p&gt;Here&apos;s the part that matters. Every decision you don&apos;t make explicitly, something else makes for you. This is not optional. It is a property of how computation works. Code has to run; the CPU has to do something; some decision gets made at every level whether you named it or not.&lt;/p&gt;
&lt;p&gt;When you don&apos;t specify register allocation, the compiler picks — usually well. When you don&apos;t specify an allocation strategy, the runtime picks — usually well. When you don&apos;t specify the invariant that balances can&apos;t go negative, the LLM picks — and here, &quot;well&quot; is doing a lot of work. The LLM will produce code. That code will make an implicit choice about whether negative balances are possible. If you didn&apos;t state the invariant, you delegated the choice, and you won&apos;t know which way it went until something breaks.&lt;/p&gt;
&lt;p&gt;The trap in modern AI coding is believing that not writing a spec is the same as not making a decision. It isn&apos;t. It&apos;s delegation to a system with opaque judgment. Sometimes that&apos;s fine — for a throwaway script, delegating everything to the model is exactly right. For a payment processor, it&apos;s catastrophic. The question is never whether to delegate. The question is which things are cheap to delegate and which things you need to nail down yourself.&lt;/p&gt;
&lt;h2&gt;Why formal specs are just another rung, not a different kind of thing&lt;/h2&gt;
&lt;p&gt;Formal methods have a reputation for being exotic — a separate discipline practiced by people in academic buildings. The approach this series is about treats them as something much more mundane: another place on the abstraction ladder you were already climbing.&lt;/p&gt;
&lt;p&gt;A Shen type rule is a way of saying &quot;here is an invariant I care about; everything else, figure out.&quot; That is structurally identical to writing a Python function signature that says &quot;here is the shape of my input and output; everything else, figure out.&quot; The Python type system is just weaker — it lets more invariants stay implicit. Sequent calculus is stronger — it lets fewer invariants stay implicit. They&apos;re doing the same job with different amounts of precision.&lt;/p&gt;
&lt;p&gt;Once you see this, the question &quot;should I write formal specs for my AI coding loop?&quot; stops being philosophical. It becomes the same question you&apos;ve been answering your whole career: which level of abstraction matches the cost of getting this wrong? For a weekend project, prompt-level is fine. For code that moves money, you want a rung where invariants are checked, not hoped for.&lt;/p&gt;
&lt;h2&gt;This is not new and that&apos;s the point&lt;/h2&gt;
&lt;p&gt;Every generation of programmers has had a version of this fight. Assembly programmers thought C hid too much. C programmers thought Java hid too much. Java programmers thought Rails hid too much. Rails programmers think prompting hides too much. In every case, the complaint has the same structure: the new layer delegates decisions that the old guard considered essential.&lt;/p&gt;
&lt;p&gt;And in every case, both sides were partly right. The new layer genuinely does hide things that sometimes matter. And the new layer genuinely does let you express intent more directly when those things don&apos;t matter. The resolution has always been the same: learn to move up and down the ladder. Pick the rung that matches the stakes. Don&apos;t mistake &quot;I didn&apos;t write it&quot; for &quot;it isn&apos;t there.&quot;&lt;/p&gt;
&lt;p&gt;AI coding is the newest rung, and formal specs are a tool for dropping back down a rung when you need to. That&apos;s all. It&apos;s not a paradigm shift. It&apos;s the same abstraction ladder you&apos;ve been on the whole time, with one new step at the top and a useful reminder that the lower steps are still available when you need them.&lt;/p&gt;
&lt;p&gt;The rest of this series is about what lives on those lower steps — deductive gates, oracles, defense in depth, and what &quot;knowing what you want&quot; actually looks like when the thing below you can write a thousand lines a minute.&lt;/p&gt;
</content:encoded></item><item><title>One Spec, Five Languages, Five Different Guarantees</title><link>https://reubenbrooks.dev/blog/the-enforcement-spectrum-guard-types-across-go-typescript-and-python/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/the-enforcement-spectrum-guard-types-across-go-typescript-and-python/</guid><description>The strength of a compile-time invariant is bounded by the weakest link in the enforcement chain — and different languages provide very different enforcement substrates.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here is a question that I think deserves more attention than it has received: if you have a formal invariant, say &quot;amounts must be non-negative,&quot; and you generate enforcement code in five different languages from that single specification, do you actually get the same guarantee in each? The answer, it turns out, is no, and not even close. I suspect most developers have an intuitive sense that languages differ in their enforcement capabilities, but the nature of the divergence is more interesting than a simple ranking of &quot;strong&quot; versus &quot;weak&quot; might suggest. Understanding where and why the guarantees differ is, I think, the key to making this approach practical rather than merely elegant.&lt;/p&gt;
&lt;h2&gt;The Spec&lt;/h2&gt;
&lt;p&gt;Consider the following specification, written in Shen&apos;s type system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shen&quot;&gt;(datatype amount
  X : number;
  (&amp;gt;= X 0) : verified;
  ====================
  X : amount;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the same regardless of target language. Shen does not care about Go or TypeScript. It is pure math: if X is a number and X is greater than or equal to zero, then X is an amount. The specification exists in a space that is, in a meaningful sense, prior to any particular language&apos;s type system. This reminds me of how mathematical proofs exist independently of the notation used to express them, though the notation can constrain what is easy or hard to express. The interesting question, then, is what happens when this abstract specification becomes concrete code in a particular language. The answer reveals something about the hidden assumptions we carry when we think about type safety.&lt;/p&gt;
&lt;h2&gt;Tier 1: The Compiler as Structural Enforcer&lt;/h2&gt;
&lt;p&gt;In languages like Go, Rust, Java, Swift, Kotlin, and C#, the compiler itself becomes the enforcement mechanism. Consider Go:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Amount struct{ v float64 }  // lowercase v = unexported

func NewAmount(x float64) (Amount, error) {
    if !(x &amp;gt;= 0) { return Amount{}, fmt.Errorf(&quot;x must be &amp;gt;= 0: %v&quot;, x) }
    return Amount{v: x}, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If code outside the package attempts &lt;code&gt;Amount{v: -5}&lt;/code&gt;, this is a compile error. Not a warning, not a lint suggestion, but a hard refusal to produce a binary. The Go compiler is the enforcer. Now, Rust takes this further in an interesting way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub struct Amount { v: f64 }  // v is private by default

impl Amount {
    pub fn new(x: f64) -&amp;gt; Result&amp;lt;Self, String&amp;gt; {
        if !(x &amp;gt;= 0.0) { return Err(format!(&quot;x must be &amp;gt;= 0: {}&quot;, x)); }
        Ok(Amount { v: x })
    }
    pub fn val(&amp;amp;self) -&amp;gt; f64 { self.v }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not only is &lt;code&gt;v&lt;/code&gt; private, but shengen can derive no &lt;code&gt;Clone&lt;/code&gt; on guarded types, preventing bypass via cloning a partially-constructed value. Rust&apos;s ownership system adds an enforcement layer that Go simply does not have. I find this instructive because it illustrates how the same formal specification, when projected onto different type systems, picks up additional guarantees almost as a byproduct of the target language&apos;s existing machinery. The ownership model was not designed for this purpose, but it serves it nonetheless.&lt;/p&gt;
&lt;p&gt;What you get in Tier 1 is that structural bypass is a compile error. The constructor is the only path to the type. The compiler enforces the specification with zero runtime overhead beyond the validation check itself. If the code compiles, the invariant holds at every construction site. There is something deeply satisfying about this, and I think it is related to why formal methods have historically found their strongest foothold in languages with rich compile-time guarantees. The enforcement is not merely conventional; it is structural.&lt;/p&gt;
&lt;h2&gt;Tier 2: Compile-Time Guarantees with an Escape Hatch&lt;/h2&gt;
&lt;p&gt;TypeScript presents a more nuanced case, and I think the nuance is worth dwelling on because it reveals a hidden premise in how we typically think about type safety.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class Amount {
    private constructor(private readonly v: number) {}
    
    static create(x: number): Amount {
        if (!(x &amp;gt;= 0)) throw new Error(`x must be &amp;gt;= 0: ${x}`);
        return new Amount(x);
    }
    
    val(): number { return this.v; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Writing &lt;code&gt;new Amount(-5)&lt;/code&gt; produces a TypeScript compile error because the constructor is private. If you are writing TypeScript and running &lt;code&gt;tsc&lt;/code&gt;, you get what appears to be a Tier 1-equivalent guarantee in your source code. But TypeScript transpiles to JavaScript, and after transpilation, &lt;code&gt;private&lt;/code&gt; disappears. In the emitted JavaScript, &lt;code&gt;new Amount(-5)&lt;/code&gt; works perfectly well. A determined developer, or an LLM that has been asked to write JavaScript rather than TypeScript, can bypass the constructor entirely. The runtime validation in &lt;code&gt;create()&lt;/code&gt; still catches &lt;code&gt;Amount.create(-5)&lt;/code&gt; because the &lt;code&gt;throw&lt;/code&gt; survives transpilation, so you get runtime enforcement even in JavaScript, but the compile-time wall has a door in it.&lt;/p&gt;
&lt;p&gt;I find this situation analogous to a concept from institutional economics: the difference between a rule that is enforced by physical constraint versus one enforced by social convention backed by occasional auditing. The TypeScript compiler provides genuine enforcement within its domain, but the transpilation boundary is a trust boundary. Code on the other side of that boundary operates under different rules entirely. Shengen for TypeScript also supports &lt;code&gt;#private&lt;/code&gt; fields using the ECMAScript private fields syntax, which does survive transpilation, though browser support and tooling are still catching up. It seems to me that the TypeScript case is particularly revealing because it forces us to ask: what exactly do we mean when we say a type system &quot;enforces&quot; something? The enforcement is real, but it is bounded by the compilation context in a way that Tier 1 languages&apos; enforcement is not.&lt;/p&gt;
&lt;h2&gt;Tier 3: Convention and Runtime&lt;/h2&gt;
&lt;p&gt;Python, Ruby, Lua, and plain JavaScript occupy a different position on this spectrum, and I think it is important to be precise about what is and is not lost.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Amount:
    def __init__(self, x: float):
        if not (x &amp;gt;= 0):
            raise ValueError(f&quot;x must be &amp;gt;= 0: {x}&quot;)
        self._v = x
    
    @property
    def val(self) -&amp;gt; float:
        return self._v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python has no compile-time privacy. Writing &lt;code&gt;amount._v = -5&lt;/code&gt; works. The underscore is a naming convention, not a language guarantee. Mypy catches type mismatches but not privacy violations. There is no compilation step that catches structural bypass. &lt;code&gt;Amount(-5)&lt;/code&gt; raises a runtime ValueError, so the validation does run, but nothing prevents post-construction mutation or direct field access. For stronger enforcement in Tier 3 languages, shengen can use closures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def new_amount(x: float):
    if not (x &amp;gt;= 0):
        raise ValueError(f&quot;x must be &amp;gt;= 0: {x}&quot;)
    def val():
        return x
    return val
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The captured &lt;code&gt;x&lt;/code&gt; has no externally accessible address. You cannot mutate it. You cannot inspect it except through &lt;code&gt;val()&lt;/code&gt;. This is genuinely stronger than the &lt;code&gt;_v&lt;/code&gt; convention, but ergonomically worse since every access becomes a function call rather than a property access. I suspect that in practice, the choice between class-based convention and closure-based enforcement in Python depends heavily on the specific context: how critical is the invariant, and how much ergonomic cost is the team willing to bear? This is not a purely technical question; it is a question about organizational trust and the probability of accidental violation, which depends on team norms and the likelihood of accidental violation.&lt;/p&gt;
&lt;h2&gt;Three Layers of Protection&lt;/h2&gt;
&lt;p&gt;Every language, regardless of tier, receives three layers of protection from this approach. The first layer is runtime validation: the constructor checks the invariant, and this works identically everywhere. The second layer is compiler enforcement, which is where languages diverge dramatically. Go and Rust produce hard compile errors for structural bypass. TypeScript produces compile errors within its own context but loses them after transpilation. Python relies on naming convention alone. The third layer is deductive verification: Shen&apos;s own type checker (&lt;code&gt;shen tc+&lt;/code&gt;) proves the consistency of the specification as a subprocess, and this too is universal because it does not depend on the target language&apos;s type system at all.&lt;/p&gt;
&lt;p&gt;Layers one and three are universal precisely because they do not depend on the target language&apos;s type system. Layer one runs in the constructor. Layer three runs in Shen. It is layer two where the interesting divergence occurs, and I think it is the most important layer for AI-assisted coding loops specifically because it is the layer that turns invariant violations into compiler errors, which are the kind of feedback that large language models are demonstrably best at acting on. There is a hidden premise here that I want to make explicit: the value of compile-time enforcement is not merely about catching errors early in some abstract sense. It is about the structure of the feedback signal. A compile error is unambiguous, localized, and actionable. A runtime error may be any of those things, or it may be none of them, depending on test coverage and the distance between the violation and its observable consequences.&lt;/p&gt;
&lt;h2&gt;Implications for AI-Assisted Development&lt;/h2&gt;
&lt;p&gt;In a Go coding loop, when an LLM writes &lt;code&gt;Amount{v: -5}&lt;/code&gt;, it receives a clear, specific error message: &quot;cannot refer to unexported field v in struct literal of type shenguard.Amount.&quot; The LLM knows exactly what to do with this. It uses &lt;code&gt;NewAmount(-5)&lt;/code&gt; instead, which returns an error, which it handles. The type system guided it to the correct path through what amounts to a structured conversation between the model and the compiler.&lt;/p&gt;
&lt;p&gt;In a Python coding loop, the same conceptual mistake does not produce a compile error. The LLM writes &lt;code&gt;Amount(-5)&lt;/code&gt;, gets a runtime ValueError, and fixes it. But it could also write &lt;code&gt;amount._v = -5&lt;/code&gt; and no tool would catch this until a test happens to exercise that particular path. This does not mean Python is useless for specification-driven development. It means the backpressure is softer: runtime errors instead of compile errors, convention instead of enforcement. The audit gate becomes correspondingly more important in Tier 3 languages. If someone edits the guard file to skip validation, the audit catches it even when the compiler cannot.&lt;/p&gt;
&lt;h2&gt;Choosing a Language for Formal Invariants&lt;/h2&gt;
&lt;p&gt;If you are choosing a language for a new project that will use AI coding with formal invariants, the enforcement spectrum matters in concrete ways. For safety-critical domains like healthcare, finance, or infrastructure, Tier 1 languages like Go or Rust make the compiler itself the enforcer, and this is probably where you want to be. For web applications with a TypeScript team, the &lt;code&gt;tsc&lt;/code&gt; guarantees are real within your codebase, and the JavaScript escape hatch matters less if you control the build pipeline. For an existing Python codebase, Tier 3 still provides genuine value: the specification documents invariants, the runtime validates them, and &lt;code&gt;shen tc+&lt;/code&gt; proves consistency. You get perhaps sixty percent of the benefit with zero language migration cost, which is a tradeoff that may well be worth making depending on the circumstances.&lt;/p&gt;
&lt;p&gt;The point, as I see it, is not that every language is equal. They manifestly are not. The point is that every language benefits from formal specification, and the specification itself is portable across all of them. Write the specification once, generate guard types for each language in your stack, and get the strongest guarantee each language can offer. This is a pragmatic position, not an idealistic one, and I think it is more honest about the messy reality of polyglot codebases than an approach that insists on a single &quot;correct&quot; language for formal methods.&lt;/p&gt;
&lt;h2&gt;The Self-Replicating Specification Chain&lt;/h2&gt;
&lt;p&gt;One final point that I think deserves attention: what about languages that shengen does not yet support? The &lt;code&gt;create-shengen&lt;/code&gt; meta-specification is an 875-line document that teaches an LLM to build a shengen for any target language. It parameterizes the enforcement mechanism (unexported fields, private constructors, closures), error handling patterns (error returns, exceptions, Result types), naming conventions, value accessor syntax, sum type implementation, and set membership syntax. A single LLM invocation with this specification produces a working shengen for a new language. The formal verification supply chain is, in a meaningful sense, self-replicating. I am not entirely sure what to make of this. It is both powerful and somewhat unsettling, in the way that any self-referential system tends to be. The specification for generating specification-enforcers is itself a specification, and the question of who verifies the verifier is one that I suspect will become increasingly important as these systems proliferate.&lt;/p&gt;
</content:encoded></item><item><title>The Spec Is Not a Gate, It Is a Substrate</title><link>https://reubenbrooks.dev/blog/the-spec-is-not-a-gate-it-is-a-substrate/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/the-spec-is-not-a-gate-it-is-a-substrate/</guid><description>Four places one .shen file can enforce itself — and why that changes what a specification is for.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This series is about Shen-Backpressure — a project that uses a small, formally-defined specification language (Shen) to constrain and guide an AI coding loop. The natural reading of a tool like this is that it adds a new kind of gate to your CI. You write a spec. The spec gets checked. If the check passes, the code advances. If it fails, the code stops. Gate in, gate out. Specifications as checkpoints.&lt;/p&gt;
&lt;p&gt;That reading is correct, and it is small. The more useful way to see what&apos;s happening here is to notice that the same &lt;code&gt;specs/core.shen&lt;/code&gt; file — the same handful of sequent-calculus rules about balances and transactions and tenants — is being enforced in four different places, by four different mechanisms, and all four are projections of one source. The spec isn&apos;t a gate. It&apos;s a substrate. Gates are just one of the surfaces it can render onto.&lt;/p&gt;
&lt;p&gt;Once you hold that frame, the engineering problem reshapes itself. You stop asking &quot;where in the pipeline should the check go?&quot; and start asking &quot;which surfaces does this invariant need to reach, and does my spec language let me project there?&quot;&lt;/p&gt;
&lt;h2&gt;The four surfaces, made concrete&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Surface one: the target language&apos;s type system, at build time.&lt;/strong&gt; The &lt;code&gt;shengen&lt;/code&gt; tool parses the spec and emits Go (or Rust, or TypeScript, or Python, or Java) where every domain type has private fields and a validating constructor. &lt;code&gt;Amount{v: -5}&lt;/code&gt; does not compile. &lt;code&gt;NewAmount(-5)&lt;/code&gt; returns an error. The Go compiler — not Shen, not the developer, not the LLM — enforces that you cannot hold an &lt;code&gt;Amount&lt;/code&gt; whose value is negative. The spec projected into the type system of the target language; the language&apos;s own compiler became your proof checker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Surface two: the spec&apos;s internal logic, at build time.&lt;/strong&gt; &lt;code&gt;shen tc+&lt;/code&gt; runs the sequent calculus over the spec itself. This does not check your code. It checks your rules. If you wrote two premises that can never be simultaneously satisfied, or a conclusion the premises don&apos;t support, &lt;code&gt;tc+&lt;/code&gt; fails. This is the layer nobody in the empirical-validation school has an answer for, because no amount of running programs tells you whether your specification is internally consistent. You need a deductive check, and &lt;code&gt;tc+&lt;/code&gt; is one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Surface three: the spec as a runtime oracle, at test time.&lt;/strong&gt; This is where &lt;code&gt;shen-derive&lt;/code&gt; lives, and it&apos;s the move I find most interesting. The same &lt;code&gt;.shen&lt;/code&gt; file that drives &lt;code&gt;shengen&lt;/code&gt; also contains &lt;code&gt;(define ...)&lt;/code&gt; blocks — the obvious-correct, slow, functional definition of what your code should compute. &lt;code&gt;shen-derive&lt;/code&gt; embeds an s-expression evaluator, runs the spec on sampled inputs, and emits a table-driven test that compares the hand-written implementation against the spec pointwise. The spec literally executes. Not as the thing that ships, but as the oracle that judges the thing that ships. If the efficient Go implementation diverges from the spec on any sampled input, the test fails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Surface four: boundary enforcement at I/O.&lt;/strong&gt; &lt;code&gt;shengen&lt;/code&gt; can emit scoped DB wrappers. A &lt;code&gt;TenantAccess&lt;/code&gt; proof produces a &lt;code&gt;TenantAccessDB&lt;/code&gt; struct where the tenant ID is captured and cannot be changed. Every query through that wrapper is automatically scoped to the verified tenant. The proof that was erected at the HTTP boundary travels all the way down to the SQL query without anyone having to remember to pass the right argument. The spec reached past compilation, past testing, into the runtime call to the database.&lt;/p&gt;
&lt;p&gt;Four surfaces. One file. Edit the spec, every surface updates on the next run. This is the thing the tool explainers don&apos;t say out loud: the architecture has collapsed what used to be four separate artifacts — type definitions, consistency checks, oracle tests, and authorization middleware — into a single source that projects into all of them.&lt;/p&gt;
&lt;h2&gt;Why this matters more than it sounds&lt;/h2&gt;
&lt;p&gt;In most production codebases, a business rule like &quot;balance must cover transaction&quot; lives in at least four places. There is a Confluence page. There is a validation function somewhere near the HTTP handler. There is a guard in the SQL layer. There are a handful of unit tests. Each of these is an independent restatement of the same rule, maintained by different people at different times with different incentives. They drift. Not because anyone is careless — because keeping four representations of the same idea synchronized is a task no process ever fully solves.&lt;/p&gt;
&lt;p&gt;The substrate architecture collapses this by construction. The rule exists in one place. All enforcement surfaces are generated from that place. Drift is impossible not because you have good discipline but because there is no second copy to drift from. This is the same bet that schema-first tooling (Protobuf, OpenAPI, database migrations from models) makes at the data-shape layer, extended from shapes to logic.&lt;/p&gt;
&lt;p&gt;The reason this newly matters in the AI-coding context is specific and important. LLMs are good at writing implementations from specs. They are bad at keeping specs and implementations synchronized across files by hand. If you generate everything downstream from one source, you don&apos;t need the model to remember the rule — you need it to produce code that the generated enforcement surfaces accept. The model can forget the invariant entirely and still not violate it, because the compiler, the test, the database wrapper, and the type checker have all been handed a projection of it.&lt;/p&gt;
&lt;p&gt;This is the deeper meaning of backpressure. Backpressure in the Ralph sense is the signal that travels back upstream when a gate rejects work. Backpressure in the substrate sense is something larger: every surface the spec reaches is a place where bad work cannot accumulate, because the surface itself is a projection of the rule. The rule doesn&apos;t need to be checked against the surface — the surface &lt;em&gt;is&lt;/em&gt; the rule, embodied in the local language of that enforcement context.&lt;/p&gt;
&lt;h2&gt;The substrate property and what it requires&lt;/h2&gt;
&lt;p&gt;For a specification language to work as a substrate, it has to have three properties. Not all spec languages have them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It has to be logic you can hand to a program, not logic you can only hand to a human.&lt;/strong&gt; A Confluence page is not a substrate. A Word document of requirements is not a substrate. An RFC is not a substrate. These are all artifacts where the rule is encoded in a form only humans can project from, which is why the four-places-drift problem is universal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It has to be cheap to parse and manipulate.&lt;/strong&gt; This is where Shen&apos;s Lisp syntax earns its keep. The spec is already an AST. Generating Go validators from it is a tree walk, not a parser project. Embedding its runtime is &quot;load the file and call eval.&quot; The substrate property is theoretically available to any formally-defined spec language, but in practice the ones with clean s-expression or similar representations are the ones where the projection tooling is cheap enough to actually build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It has to be expressive enough to span the surfaces you care about.&lt;/strong&gt; A schema language like Protobuf handles data shapes beautifully but can&apos;t express &quot;balance must cover transaction&quot; as a relation across fields. A type system like Rust&apos;s handles static structural constraints but can&apos;t run at test time as an oracle. Shen&apos;s sequent calculus plus its executable &lt;code&gt;(define ...)&lt;/code&gt; blocks sits in a sweet spot: it can express predicates, relations, proofs, and computations, and all three are parsable by the same tools.&lt;/p&gt;
&lt;p&gt;The interesting question for anyone building in this space is not &quot;which spec language is most rigorous?&quot; but &quot;which spec language projects cleanly into the most enforcement surfaces I need?&quot; Rigor that can only enforce at the build-time boundary is a gate. Rigor that enforces at four boundaries is a substrate. The value of a specification scales with the number of surfaces it can reach, and the ceiling of that scaling is set by the language&apos;s portability, not its theoretical strength.&lt;/p&gt;
&lt;h2&gt;The shape this implies&lt;/h2&gt;
&lt;p&gt;If you take the substrate view seriously, a few consequences follow.&lt;/p&gt;
&lt;p&gt;The spec file becomes the single most important artifact in the repository. Everything else is derived, regenerable, disposable. Editing generated code is a category error — you&apos;ve stopped editing the substrate and started editing one of its projections, which will be erased on the next run. The discipline the repo describes (&quot;never edit &lt;code&gt;guards_gen.go&lt;/code&gt;&quot;) is not arbitrary; it&apos;s the logical consequence of treating the spec as source.&lt;/p&gt;
&lt;p&gt;The number of enforcement surfaces should grow over time. If the spec is a substrate and you&apos;ve only projected it into two places, you&apos;ve left value on the table. The repo&apos;s trajectory — from &lt;code&gt;shengen&lt;/code&gt;&apos;s type generation to scoped DB wrappers to &lt;code&gt;shen-derive&lt;/code&gt;&apos;s runtime oracle — is the natural shape of a project whose authors have started asking &quot;where else can this rule reach?&quot; That question has no fixed answer. Every new surface is a new place where drift becomes impossible.&lt;/p&gt;
&lt;p&gt;Specification skills become more valuable, and implementation skills become less so. If one spec file can drive four projections, the leverage on the spec is four times the leverage on any individual projection. The person who can write a precise, minimal, well-factored spec is doing something structurally different from the person who can write good Go. This is not a new idea — it&apos;s been true since the first DSL compiler — but the AI-coding context sharpens it. The LLM can write the Go. What it cannot do for you is decide which invariants the Go must respect. That decision lives in the spec, and the spec is now the thing every enforcement surface is answering to.&lt;/p&gt;
&lt;p&gt;The right mental model for a tool like Shen-Backpressure is not &quot;a type checker with a codegen step.&quot; It&apos;s &quot;a system for projecting a single logical artifact into every context where it can do useful work.&quot; The gates are one projection. The runtime oracle is another. The DB wrappers are a third. The consistency check is a fourth. There will be more. That&apos;s the point.&lt;/p&gt;
</content:encoded></item><item><title>The Spec Is the Oracle, Not the Generator</title><link>https://reubenbrooks.dev/blog/the-spec-is-the-oracle-not-the-generator/</link><guid isPermaLink="true">https://reubenbrooks.dev/blog/the-spec-is-the-oracle-not-the-generator/</guid><description>An inversion buried in the shen-derive pivot that generalizes to almost every verified-code project.</description><pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Shen-Backpressure — the project this series is about — contains a tool called &lt;code&gt;shen-derive&lt;/code&gt;. There&apos;s a note in its design doc that I think contains one of the most important small ideas in the repository. It describes why the tool was rewritten. The v1 version was a code generator — a Bird-Meertens rewrite engine that parsed a typed lambda calculus, applied algebraic laws like foldr-fusion and map-fusion, and lowered the result into idiomatic Go loops. It worked. It hit a wall. The v2 version threw the code generator away and kept the evaluator. The spec, which had been the input to the code generator, became the oracle that judges a hand-written implementation.&lt;/p&gt;
&lt;p&gt;The note frames this as an engineering pivot. I think it&apos;s a deeper point that quietly generalizes to almost every formal-methods project in history, and I want to unpack why.&lt;/p&gt;
&lt;h2&gt;Two ways to use a precise specification&lt;/h2&gt;
&lt;p&gt;Any precise, executable specification admits two different uses. You can generate implementations from it, or you can judge implementations against it. These look similar. They are not.&lt;/p&gt;
&lt;p&gt;When you &lt;strong&gt;generate&lt;/strong&gt;, you start with the spec and mechanically derive a target-language implementation. The rewrite engine walks the spec, applies transformations, produces code. The code is correct by construction, because every step in the derivation is sound. This is the Bird-Meertens dream; it&apos;s also the Coq-extraction dream, the Lean-to-binary dream, the &quot;compile my proof to executable code&quot; dream.&lt;/p&gt;
&lt;p&gt;When you &lt;strong&gt;judge&lt;/strong&gt;, you start with the implementation — written however you like, by whoever you like — and run the spec against it. You pick sample inputs. You evaluate the spec. You evaluate the implementation. If they disagree anywhere, the implementation fails. The spec never becomes the code; it becomes the thing the code has to answer to.&lt;/p&gt;
&lt;p&gt;Both approaches use the same spec. Both end up with verified code. The difference is which direction the verification arrow points.&lt;/p&gt;
&lt;h2&gt;Why generation hits a ceiling&lt;/h2&gt;
&lt;p&gt;Generation from a spec sounds like the right move. It has proof-by-construction; it has no gap between the verified object and the shipping object; it gives you the fantasy of a language where &quot;correct&quot; and &quot;efficient&quot; are the same file. The people who try it are not fools. It&apos;s the reasonable thing to try.&lt;/p&gt;
&lt;p&gt;It hits a ceiling for a reason that has nothing to do with any particular project. The ceiling is that the set of programs you can generate from a spec is bounded by the rewrite laws and lowering patterns you have implemented. Adding a new shape of computation means adding a new law. And a new lowering pattern. And proving the law sound. And testing the lowering. Every domain-specific wrinkle — guard types, constrained values, field accessors, performance idioms — fights the type system of the rewrite engine. The catalog grows, the engine gets more complex, and the set of specs it accepts gets more fragile rather than more expressive. You end up with a very clever thing that handles exactly the cases its authors thought of.&lt;/p&gt;
&lt;p&gt;This is not a bug in any one rewrite engine. It is the shape of the problem. Generation is constructive — you are building the output from parts — and the parts have to cover the whole space of outputs you want. If your spec can express a shape of computation your engine cannot lower, you are stuck. Either you extend the engine (at cost) or you constrain the spec (at cost). Neither path scales.&lt;/p&gt;
&lt;h2&gt;Why judgment does not&lt;/h2&gt;
&lt;p&gt;The judgment approach — spec as oracle, implementation written freely — has the opposite shape. The set of programs you can verify is not bounded by the transformations you can perform. It is bounded only by what your spec can express. If Shen can say it, &lt;code&gt;shen-derive&lt;/code&gt; can judge something against it. The implementation can use any idiom, any data structure, any optimization the author (or LLM) wants, as long as the outputs match the spec&apos;s outputs on the sampled inputs.&lt;/p&gt;
&lt;p&gt;The ceiling has moved. Instead of being the expressiveness of your lowering patterns, it is the expressiveness of your spec language. For any reasonable spec language, that ceiling is much higher. You trade constructive correctness (the output came from the spec, so it&apos;s right by derivation) for pointwise correctness (the output matches the spec on the inputs we checked). You lose a theoretical guarantee and gain an enormous amount of practical reach.&lt;/p&gt;
&lt;p&gt;This is the same trade that made property-based testing eat formal methods in industry. QuickCheck didn&apos;t win because generators of correct-by-construction programs were inadequate; it won because you could hand it a predicate about any program you had already written and get immediate feedback. It met code where code lived. The predicate-as-oracle approach is small, late-binding, and scales to whatever program you can describe.&lt;/p&gt;
&lt;p&gt;The deeper point: precise specifications are more useful as judges than as constructors. The constructive path always has a ceiling that is the sum of the transformations you&apos;ve implemented. The judgmental path has a ceiling that is only the expressiveness of the spec. In almost every engineering context, expressiveness is cheaper to add than transformations are.&lt;/p&gt;
&lt;h2&gt;The AI-coding twist&lt;/h2&gt;
&lt;p&gt;This inversion becomes sharper in the AI-coding setting, because the AI changes which end of the pipeline is cheap.&lt;/p&gt;
&lt;p&gt;In the pre-AI world, the argument for generation was partly an argument about human effort. Humans are slow to write implementations. Humans make transcription errors. A code generator — even one with a low ceiling — saves labor in the region where it works. The fact that it doesn&apos;t scale to the full expressiveness of the spec language is a real cost but a bounded one; the generator does the 80% and humans handle the long tail.&lt;/p&gt;
&lt;p&gt;In the AI-coding world, implementations are cheap. The LLM can write Go fast, and it can write variations of Go fast, and it can rewrite Go after feedback. What is expensive is not producing code — it is producing code that respects an invariant. The bottleneck moves from writing to judging.&lt;/p&gt;
&lt;p&gt;This is precisely where the oracle model wins and the generator model loses. The oracle model treats the implementation as disposable — write it, judge it, rewrite it, judge again — and concentrates the precise work on the spec. The generator model treats the spec as disposable — write it, compile it, ship the output — and concentrates the precise work on the lowering engine. In an environment where implementations are nearly free and invariants are not, you want the precise work concentrated on the invariants.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;shen-derive&lt;/code&gt; pivot is really the repo noticing this and adjusting. The spec still exists, still matters, and still has to be right. But the role of the spec has moved. It is no longer the thing the implementation is derived from. It is the thing the implementation is judged against. The implementation is produced freely — by the LLM, by the human, by whatever process can put bytes on disk — and the spec&apos;s job is to say yes or no.&lt;/p&gt;
&lt;h2&gt;The generalization&lt;/h2&gt;
&lt;p&gt;I think this is the right way to see almost every formal-verification project.&lt;/p&gt;
&lt;p&gt;If your spec language can both generate and judge, judge. The set of programs you can verify is larger, the toolchain is simpler, the spec can stay the same, and the thing being verified is the thing that ships. The hypothetical benefit of proof-by-construction is usually not worth the ceiling it imposes on what you can express.&lt;/p&gt;
&lt;p&gt;If your spec language can only generate — if it&apos;s a code generator posing as a verification tool — the ceiling is real and will be hit. You can extend the catalog forever and always discover the next case you haven&apos;t covered. The path out is not more laws; it&apos;s a richer spec and a separate, simpler judgment harness.&lt;/p&gt;
&lt;p&gt;If your spec language can only judge — if it&apos;s a predicate language without a code-generation story — you have most of what you want. The thing you are missing (generating implementations) was mostly a luxury anyway, and the LLM can fill it in cheaper than the generator could.&lt;/p&gt;
&lt;p&gt;The spec is the oracle. Anything else you do with it is optional. The repo&apos;s pivot from rewrite engine to verification gate is not a retreat; it&apos;s an alignment with how precise specifications actually deliver value in programs people ship.&lt;/p&gt;
</content:encoded></item></channel></rss>