← Principles

Stack Coverage

Where each primitive applies, where the gaps are.

Layer-by-layer assessment

Layer Derived obligations Prescriptive failure Bundled enforcement Vacuity detection
Database schema Schema IS the spec Migration fails if invalid Typed query results Golden file for schema
Data access sqlc from SQL Shows expected signature Typed return values N/A (generated code)
Business logic Gap, no codegen Harder here (see below) Method signatures Property-based testing
API layer Huma from structs Generated OpenAPI shows contract Validated typed input Schema validation
Frontend types openapi-typescript TS compiler errors Types enforce API contract Snapshot tests
Tests Spec-derived requirements Panic names the method to call Data bundled with enforcement Blank-node test, golden files
CI/CD Pipeline from config Specific remediation messages Pipeline won't pass without checks File size limits, coverage gates
Config Typed struct "missing field X of type Y" Config struct is only access path Zero-value detection

The business logic gap

Business logic is the weakest layer. There is no codegen pipeline for "derive correct business logic from spec." The LLM has to reason about it. This is where 83% of LLM errors live, the logic errors that no compiler catches.

Mitigations (weaker than the primitives, but they help):

Exhaustive enum matching. In Rust, match is exhaustive by default. In Go, use the exhaustive linter for switch statements. When you add a new status value, every switch that doesn't handle it fails CI. This turns a class of logic errors into type errors.

Strong domain types. type UserID int64 and type OrderID int64 are different types in Go. The compiler prevents passing one where the other is expected. This narrows what the LLM can express.

Property-based testing. rapid in Go, proptest in Rust. Instead of writing examples, declare invariants ("output is sorted," "output is a subset of input"). The framework generates thousands of inputs and checks every one. Brief, binary, verifiable failure when an invariant breaks.

Small, pure functions. Minimize the reasoning surface per unit. A 20-line pure function with typed inputs and outputs is easier for the LLM to get right than a 100-line method with side effects.

The derivation chain

The ideal architecture is a chain where each layer's output is derived from the layer above:

SQL schema (hand-written)
  -> sqlc generates Go query functions
    -> Huma derives OpenAPI from handler structs
      -> openapi-typescript generates TS types

The LLM writes SQL and Go structs. Three layers of type-safe code are computed. If the SQL is wrong, sqlc refuses to generate. If the struct is wrong, Huma refuses to register. Each arrow is a layer where the LLM cannot produce wrong output.

The chain breaks at business logic. Between "data access" and "API layer," the LLM has to write the logic that transforms data into responses. That's the reasoning surface. The primitives narrow the inputs and outputs of that surface; they can't eliminate the reasoning itself.

Coverage by language

Principle Go Rust
Derived obligations sqlc, Huma, buf diesel/sqlx codegen, tonic, axum
Prescriptive failure Custom error messages, golangci-lint Compiler errors with codes, clippy
Bundled enforcement t.Cleanup, typed returns Drop trait, MustInspect, typestate
Vacuity detection Golden files, property testing Blank-node pattern, property testing

Rust has a stronger type-system story for bundled enforcement (the Drop trait is built in; Go requires discipline). But Rust's verbose errors (15-30 lines per borrow checker message) accelerate the debugging decay that DDI measured. Go's terser errors align better with the prescriptive failure principle.

Both languages can implement all four primitives. The tooling differs but the architectural patterns are the same.