# Deepening

How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in
[SKILL.md](SKILL.md): **module**, **interface**, **seam**, **adapter**.

## Dependency categories

Classify a candidate's dependencies before deepening it. The category decides how the deepened module
is tested across its seam.

| Category | What it is | How it is tested |
|---|---|---|
| **In-process** | Pure computation, in-memory state, no I/O | Merge the modules, test through the new interface directly. No adapter needed. |
| **Local-substitutable** | Has a local stand-in (PGLite for Postgres, an in-memory filesystem, a framework's own local test backend) | Deepenable once the stand-in exists. The stand-in runs in the test suite; the seam stays internal, with no port at the external interface. |
| **Remote but owned** | Your own services across a network boundary | Define a **port** at the seam. The deep module owns the logic; the transport is an injected **adapter**. In-memory adapter in tests, HTTP/RPC/queue adapter in production. |
| **True external** | Third-party services you do not control | The module takes the dependency as an injected port; tests supply a mock adapter. |

Where the categories usually land in this stack: pure TypeScript logic is in-process; a database with
a local runner is local-substitutable; Convex functions called across the network from a Next.js
client are remote-but-owned; Jira, Infisical, Stripe, and similar are true external. Confirm what
local test harness a framework actually ships before assuming one exists.

The recommendation for a remote-but-owned dependency reads: *"Define a port at the seam, implement an
HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep
module even though it is deployed across a network."*

## Seam discipline

- **One adapter means a hypothetical seam. Two adapters means a real one.** Do not introduce a port
  unless at least two adapters are justified, typically production plus test. A single-adapter seam
  is indirection wearing a design's clothes.
- **Internal seams are not external seams.** A deep module may have internal seams, private to its
  implementation and used by its own tests. Do not expose one through the interface merely because a
  test reaches for it.

## Testing strategy: replace, do not layer

- Old unit tests on the shallow modules become waste once tests exist at the deepened module's
  interface. Delete them; leaving both is how a suite doubles in size while covering the same
  behaviour twice.
- Write the new tests at the deepened module's interface. **The interface is the test surface.**
- Assert on observable outcomes through the interface, never on internal state.
- A test that must change when the implementation changes is testing past the interface. That is the
  tell, and the fix is the test, not the module.
