3.1 KiB
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.