# 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.