Architecture Note · 12

The test that proves the architecture

ledger · Layer Design · June 2026 · Eric Tetzlaff

I built ledger to solve a specific problem: I was losing the "why" behind architectural decisions the moment I moved to the next project. Not the decisions themselves — those live in the code. The reasoning. Why SQLite over PostgreSQL. Why keyword overlap before embeddings. Why the MCP server is read-only. That reasoning was living nowhere, and six months later it was gone.

The tool I built has two layers. Layer 1 is the deterministic core: capture, store, list, filter, show, supersede. Zero runtime dependencies. No network. No API key. If you pip install ledger without any extras, you get a fully functional decision store backed by SQLite — which ships with Python. Layer 2 is optional AI retrieval: natural-language recall, project re-orientation briefs, synthesis calls to Anthropic Haiku. Install the [ai] extra and Layer 2 turns on. Don't install it and Layer 1 keeps working — the retrieval degrades to structured search and tells you so.

Figure 1 — The two-layer boundary
Layer 2 — Intelligent Retrieval
recall · resume · Anthropic Haiku synthesis · keyword rank → bounded hydration
pip install -e ".[ai]"
Layer 1 — Deterministic Core
capture · store · list · filter · show · supersede · SQLite only
Zero deps
The green edge marks the guarantee: Layer 1 is complete without Layer 2, the dependency runs one way only, and a test in the suite fails if that direction is ever reversed.

The boundary is clear in the design. The problem is that design boundaries are easy to describe and easy to violate. An accidental import in a utility function. A refactor that moved a helper into the wrong module. A transitive dependency that pulls in the AI layer without anyone noticing. The boundary exists in the README until it doesn't, and the only way you find out it was crossed is when a user installs without the AI extra and something breaks.

A comment saying "never import AI here" can be ignored. A failing test cannot.

The solution in ledger is a single test that isn't about behavior at all:

Figure 2 — The boundary, enforced as a test
def test_core_imports_no_anthropic(): """The boundary, enforced as a test: importing core must not pull in the AI layer.""" import importlib, sys importlib.import_module("ledger.core.capture") importlib.import_module("ledger.core.query") assert "anthropic" not in sys.modules, ( "core/ imported the AI layer — degradation guarantee is broken" )
Nothing here asserts what the code does. It imports the two core modules and fails if the Anthropic SDK reached sys.modules, which makes the layer boundary a build condition.

This test doesn't verify what the code does. It verifies what the code is — specifically, what it refuses to depend on. Import the two core modules. Check that the Anthropic SDK never made it into sys.modules. If the boundary was crossed at any point in either import chain, the test fails. Red. At pytest. Before it reaches anyone else's machine.

The test is the architecture, enforced. Not described — enforced.

This is the same principle I apply to AI system guardrails: if the behavior genuinely cannot happen, you don't need a prompt telling the model not to do it. Build the impossibility into the system. A system whose safety depends on correctly applying a "never" instruction at inference time doesn't have a guardrail — it has a suggestion. The same logic applies to your own codebase's structural constraints. A comment is a suggestion. A test is a constraint.

What else changed because of this test

The directory structure became load-bearing. The retrieval module had to live at ledger/retrieval/, not ledger/core/. Not because of a naming convention — because the test made the wrong location a failing build. The directory structure now encodes the layer boundary. You can read the architecture from the file tree without opening a single file.

The test also defined what "degradation" means concretely. Before writing it, "Layer 1 works without AI" was a claim. After writing it, it became a property that the test suite verifies on every run. The claim became a guarantee. That's a different category of statement.

One more thing I noticed after writing it: every other test in the file tests behavior. Round-trip capture and retrieval. Supersession chain linking. Filter correctness by project and status. All of those tests answer "does the code do what it's supposed to do?"

test_core_imports_no_anthropic answers a different question: "is the code what it's supposed to be?" Behavioral tests verify functionality. Structural tests verify architecture. Both are necessary. Most projects only write the first kind.

The architecture of ledger is a claim about separation. The test is what makes that claim true.

← Architecture Note · 11 Next post →