# ADR-WS-001: The WebSocket platform ships as packages in one leaf, with machine-checked boundaries - Status: accepted - Date: 2026-08-25 - Scope: `:adapter:inbound:websocket` ## Context The realtime connection platform design models itself as eighteen Gradle modules under `modules/websocket`, each with a declared purity grade and a declared set of allowed dependencies. This repository's `src/config/architecture/modules.json` is a fail-closed registry that owns the leaf list; adding eighteen leaves is a registry change of a size that needs its own decision, and HARD-STOP #5 forbids doing it implicitly. Three earlier platforms in this repository — JPA, GraphQL, and the HTTP platform — met the same situation and resolved it the same way. ## Decision The eighteen design modules ship as packages inside the single registered leaf. `WebSocketStableModule` declares each one's package, purity grade and exact allowed edges, and `WebSocketModuleBoundaryTest` scans the production tree and fails when the declaration and the tree disagree in either direction. Three deviations from the design's module map were forced by the check and are recorded in `docs/websocket/repository-adaptation.md`: `WebSocketSubprotocolName` moved to `core` and the codec moved to its own FRAMEWORK_BOUND module, both to avoid cycles the design's placement created here; and the `budget -> core` edge was inverted because `budget` imports nothing from `core`. ## Consequences **The boundary is enforced, not documented.** Six violations were caught during implementation that a document would not have: two would-be cycles, a duplicate module declaration where two ids claimed one package, and three undeclared edges. The duplicate is the instructive one — with two ids on one package, ownership depends on iteration order and one module's rules silently apply to nothing. A guard against it is now part of the boundary test. **The detector had a hole.** Its framework-import list named `com.fasterxml` (Jackson 2) and not `tools.jackson` (Jackson 3), which is what Spring 7 actually uses — so a CORE module could have imported a mapper unnoticed. Fixed here and in the HTTP platform, which shared the list. **Promotion stays cheap.** Each enum constant is already shaped like a leaf specification, so splitting one out later is a registry edit rather than an archaeology exercise. **The design's own rules were kept where they cost something.** `core` names no framework, so the same decisions serve both runtimes and are testable without a server; no Java class name reaches the wire; the payload is an encoded string rather than a map; and handlers are given no way to write, which is what makes ordering and backpressure guarantees rather than conventions. ## Alternatives considered **Register eighteen leaves.** Faithful to the design and a large change to a fail-closed registry for a platform that ships as one artifact either way. Rejected as disproportionate; the boundary test provides the property the modules were for. **Ship the modules as packages with no enforcement.** Cheapest, and it makes the boundary a claim. The six violations found during implementation are the argument against it.