@lvlup-sw/axiom 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,319 @@
1
+ # Dependency Patterns Reference
2
+
3
+ Coupling metrics, dependency direction analysis, and circular dependency detection for architecture review.
4
+
5
+ ---
6
+
7
+ ## Healthy vs Unhealthy Dependency Patterns
8
+
9
+ ### Healthy: Inward-Pointing Dependencies
10
+
11
+ Dependencies flow from outer layers (infrastructure, I/O, frameworks) toward inner layers (domain, core business logic). The core has no knowledge of the outer layers.
12
+
13
+ ```text
14
+ +-------------------------------------------------------+
15
+ | Infrastructure |
16
+ | (HTTP, Database, Filesystem, External APIs) |
17
+ | |
18
+ | +-----------------------------------------------+ |
19
+ | | Application Layer | |
20
+ | | (Use Cases, Orchestration, Commands) | |
21
+ | | | |
22
+ | | +---------------------------------------+ | |
23
+ | | | Domain Core | | |
24
+ | | | (Entities, Value Objects, Rules) | | |
25
+ | | | | | |
26
+ | | | * No imports from outer layers | | |
27
+ | | | * Defines interfaces others impl | | |
28
+ | | +---------------------------------------+ | |
29
+ | | | |
30
+ | +-----------------------------------------------+ |
31
+ | |
32
+ +-------------------------------------------------------+
33
+
34
+ Dependency direction: Infrastructure --> Application --> Domain
35
+ (arrows point INWARD)
36
+ ```
37
+
38
+ **Characteristics of healthy patterns:**
39
+ - Domain core has zero imports from infrastructure or framework code
40
+ - Interfaces are defined in the domain, implemented in infrastructure
41
+ - The composition root (entry point) is the only place that wires concrete implementations
42
+ - Modules at the same layer communicate through well-defined interfaces
43
+
44
+ ### Unhealthy: Outward-Pointing Dependencies
45
+
46
+ Domain or core modules import directly from infrastructure, creating tight coupling.
47
+
48
+ ```text
49
+ +-------------------------------------------------------+
50
+ | Infrastructure |
51
+ | (HTTP, Database, Filesystem, External APIs) |
52
+ | |
53
+ | +-----------------------------------------------+ |
54
+ | | Application Layer | |
55
+ | | | |
56
+ | | +---------------------------------------+ | |
57
+ | | | Domain Core | | |
58
+ | | | | | |
59
+ | | | import { Pool } from 'pg' <---+---+---+----- VIOLATION
60
+ | | | import { S3 } from 'aws-sdk' <---+---+---+----- VIOLATION
61
+ | | | import { readFile } from 'fs' <---+---+---+----- VIOLATION
62
+ | | | | | |
63
+ | | +---------------------------------------+ | |
64
+ | | | |
65
+ | +-----------------------------------------------+ |
66
+ | |
67
+ +-------------------------------------------------------+
68
+
69
+ Dependency direction: Domain --> Infrastructure
70
+ (arrows point OUTWARD = unhealthy)
71
+ ```
72
+
73
+ **Symptoms of unhealthy patterns:**
74
+ - Business logic modules import database drivers, HTTP clients, or framework packages
75
+ - Changing an infrastructure library forces changes in domain code
76
+ - Unit-testing domain logic requires mocking infrastructure dependencies
77
+ - Module-level globals hold infrastructure state (lazy-init singletons)
78
+
79
+ ### Unhealthy: Peer-to-Peer Coupling
80
+
81
+ Modules at the same layer bypass interfaces and depend on each other's internals.
82
+
83
+ ```text
84
+ Module A <---------> Module B
85
+ | |
86
+ +-------> Module C <-+
87
+ |
88
+ +-------> Module A (circular!)
89
+
90
+ Every module knows about every other module's internals.
91
+ Changes propagate unpredictably.
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Coupling Metrics
97
+
98
+ ### Afferent Coupling (Ca)
99
+
100
+ **Definition:** The number of external modules that depend on (import from) a given module.
101
+
102
+ **Interpretation:**
103
+ - High Ca = many dependents = this module is heavily relied upon
104
+ - Modules with high Ca should be very stable (changes break many consumers)
105
+ - If a high-Ca module is also frequently changing, it is a fragility risk
106
+
107
+ **How to measure:**
108
+ ```text
109
+ For module M:
110
+ Ca(M) = count of unique modules that import from M
111
+ ```
112
+
113
+ ### Efferent Coupling (Ce)
114
+
115
+ **Definition:** The number of external modules that a given module depends on (imports).
116
+
117
+ **Interpretation:**
118
+ - High Ce = many dependencies = this module is vulnerable to upstream changes
119
+ - Modules with high Ce are hard to test in isolation
120
+ - High Ce in a core module signals possible DIP violation
121
+
122
+ **How to measure:**
123
+ ```text
124
+ For module M:
125
+ Ce(M) = count of unique modules that M imports from
126
+ ```
127
+
128
+ ### Instability (I)
129
+
130
+ **Definition:** `I = Ce / (Ca + Ce)` where 0 means maximally stable and 1 means maximally unstable.
131
+
132
+ **Interpretation:**
133
+
134
+ | I value | Meaning | Expectation |
135
+ |---------|---------|-------------|
136
+ | I = 0 | Maximally stable | Many dependents, no dependencies. Hard to change. Should be abstract. |
137
+ | I = 1 | Maximally unstable | No dependents, many dependencies. Easy to change. Should be concrete. |
138
+ | 0 < I < 1 | Mixed | Assess whether stability matches the module's role. |
139
+
140
+ **The Stable Dependencies Principle:** Modules should depend only on modules that are more stable than themselves. An unstable module (I near 1) depending on another unstable module creates fragility chains.
141
+
142
+ ```text
143
+ STABLE (I=0.1) <---- UNSTABLE (I=0.8) OK: unstable depends on stable
144
+ STABLE (I=0.1) ----> UNSTABLE (I=0.8) BAD: stable depends on unstable
145
+ ```
146
+
147
+ ### Abstractness (A)
148
+
149
+ **Definition:** `A = abstract_types / total_types` where 0 means fully concrete and 1 means fully abstract.
150
+
151
+ **Interpretation:**
152
+ - High abstractness = mostly interfaces, abstract classes, type definitions
153
+ - Low abstractness = mostly concrete implementations
154
+
155
+ ### The Main Sequence
156
+
157
+ The ideal relationship between abstractness (A) and instability (I) follows the "main sequence" diagonal:
158
+
159
+ ```text
160
+ A (Abstractness)
161
+ 1 | Zone of .
162
+ | Uselessness .
163
+ | . Main Sequence
164
+ | . (A + I = 1)
165
+ | .
166
+ | .
167
+ | .
168
+ | . Zone of
169
+ | . Pain
170
+ | .
171
+ 0 +---+---+---+---+----> I (Instability)
172
+ 0 1
173
+ ```
174
+
175
+ - **Zone of Pain (low A, low I):** Concrete and stable. Hard to change but heavily depended on. Database schemas, core utilities.
176
+ - **Zone of Uselessness (high A, high I):** Abstract and unstable. Interfaces nobody implements. Dead abstractions.
177
+ - **Main Sequence (A + I ~ 1):** Balanced. Stable modules are abstract; unstable modules are concrete.
178
+
179
+ **Distance from Main Sequence:** `D = |A + I - 1|` — closer to 0 is better. Modules with D > 0.5 deserve investigation.
180
+
181
+ ---
182
+
183
+ ## Circular Dependency Detection
184
+
185
+ ### What Are Circular Dependencies?
186
+
187
+ A circular dependency exists when module A depends on module B, and module B (directly or transitively) depends back on module A.
188
+
189
+ ### Types of Circular Dependencies
190
+
191
+ **Direct cycles:**
192
+ ```text
193
+ A ----imports----> B
194
+ B ----imports----> A
195
+ ```
196
+
197
+ **Transitive cycles:**
198
+ ```text
199
+ A ----imports----> B
200
+ B ----imports----> C
201
+ C ----imports----> A
202
+ ```
203
+
204
+ **Barrel-file-mediated cycles:**
205
+ ```text
206
+ feature/index.ts re-exports from:
207
+ - feature/handler.ts
208
+ - feature/types.ts
209
+
210
+ feature/handler.ts imports from feature/index.ts
211
+ (to get types — but barrel re-exports handler too!)
212
+ ```
213
+
214
+ ### Detection Approach
215
+
216
+ 1. **Build the import graph:** Parse all source files, extract import/require statements, resolve to file paths
217
+ 2. **Run cycle detection:** Apply depth-first search (DFS) with back-edge detection on the import graph
218
+ 3. **Classify cycles:** Direct (2 modules), short transitive (3-4 modules), long transitive (5+ modules)
219
+ 4. **Assess severity:**
220
+ - Direct cycles involving core/domain modules: **HIGH**
221
+ - Barrel-file-mediated cycles: **MEDIUM** (often accidental, easy to fix)
222
+ - Transitive cycles in leaf modules: **LOW**
223
+
224
+ ### Remediation Strategies
225
+
226
+ | Pattern | Fix |
227
+ |---------|-----|
228
+ | Direct A <-> B cycle | Extract shared types into a third module C, both A and B depend on C |
229
+ | Barrel-file cycle | Use direct imports instead of barrel re-exports |
230
+ | Transitive cycle through shared state | Introduce an event bus or mediator pattern |
231
+ | Cycle caused by type imports only | Use `import type` (TypeScript) to break the runtime cycle |
232
+
233
+ ---
234
+
235
+ ## Layered Architecture Violations
236
+
237
+ ### What Constitutes a Layer
238
+
239
+ A layered architecture organizes code into horizontal layers with strict dependency rules:
240
+
241
+ ```text
242
+ +-------------------------------------------------------+
243
+ | Infrastructure | | Presentation |
244
+ | (DB, FS, APIs) | | (routes, controllers) |
245
+ +----------|-----+--------------+-----|------------------+
246
+ | |
247
+ v v
248
+ +-----------------------------------------------+
249
+ | Application |
250
+ | (use cases, orchestrators, commands) |
251
+ +----------------------|------------------------+
252
+ |
253
+ v
254
+ +-----------------------------------------------+
255
+ | Domain (core) |
256
+ | (entities, value objects, domain services) |
257
+ +-----------------------------------------------+
258
+
259
+ Arrows = dependency direction (pointing INWARD toward the core).
260
+ Infrastructure and Presentation both depend inward on Application/Domain.
261
+ Domain (core) NEVER imports from any outer layer.
262
+ ```
263
+
264
+ ### Common Layer Violations
265
+
266
+ | Violation | Signal | Severity |
267
+ |-----------|--------|----------|
268
+ | Domain imports infrastructure | `import { query } from '../db'` in a domain file | **HIGH** |
269
+ | Application imports presentation | Use case module imports an HTTP request type | **MEDIUM** |
270
+ | Skip-layer dependency | Presentation directly calls infrastructure, bypassing application | **MEDIUM** |
271
+ | Bidirectional layer dependency | Application imports domain AND domain imports application | **HIGH** |
272
+
273
+ ### How to Detect Layer Violations
274
+
275
+ 1. **Establish layer boundaries:** Map directories to layers (e.g., `src/domain/`, `src/infra/`, `src/app/`)
276
+ 2. **Build import graph with layer annotations:** Tag each module with its layer
277
+ 3. **Check direction:** For each import, verify the importing module's layer is the same or closer to the periphery than the imported module's layer
278
+ 4. **Flag violations:** Any import pointing outward (from a core layer to a peripheral layer) is a violation
279
+
280
+ ---
281
+
282
+ ## Dependency Inversion in Practice
283
+
284
+ ### When to Use Interfaces vs Direct Dependencies
285
+
286
+ **Use an interface (abstraction boundary) when:**
287
+ - The dependency crosses an architectural layer boundary (e.g., application -> infrastructure)
288
+ - You need to swap implementations (testing, multi-environment, migration)
289
+ - The dependency is volatile (external API, third-party library likely to change)
290
+ - Multiple implementations exist or are planned (e.g., FileStorage: local, S3, GCS)
291
+
292
+ **Use a direct dependency when:**
293
+ - Both modules are in the same layer and same bounded context
294
+ - The dependency is a stable, well-tested utility (e.g., a date library, a hash function)
295
+ - The abstraction would be a 1:1 mirror of the concrete API (pointless indirection)
296
+ - The module is a pure function or value object with no side effects
297
+
298
+ ### Practical Decision Flowchart
299
+
300
+ ```text
301
+ Does the dependency cross a layer boundary?
302
+ | |
303
+ YES NO
304
+ | |
305
+ Use interface Is the dependency volatile?
306
+ | |
307
+ YES NO
308
+ | |
309
+ Use interface Direct dependency is fine
310
+ ```
311
+
312
+ ### Common Anti-Patterns
313
+
314
+ | Anti-Pattern | Description | Fix |
315
+ |-------------|-------------|-----|
316
+ | Interface mirroring | Interface is an exact copy of one concrete class | Remove interface, use direct dependency |
317
+ | God interface | One interface with 15+ methods for all possible operations | Split into role-specific interfaces (ISP) |
318
+ | Premature abstraction | Interface created "just in case" with only one implementation | Remove until a second implementation materializes |
319
+ | Leaky abstraction | Interface exposes implementation details (SQL in method names, HTTP status codes in domain interface) | Redesign interface using domain language |
@@ -0,0 +1,359 @@
1
+ # SOLID Principles Reference
2
+
3
+ Detection heuristics and severity guidance for agent-driven architectural assessment.
4
+
5
+ ---
6
+
7
+ ## S — Single Responsibility Principle (SRP)
8
+
9
+ ### Definition
10
+
11
+ A module should have one, and only one, reason to change. Each class or module should encapsulate a single concern so that a change in one business requirement affects only one module.
12
+
13
+ ### Violation Signals
14
+
15
+ 1. A class or module handles both data persistence AND business logic
16
+ 2. A single file contains multiple unrelated public interfaces or exported functions
17
+ 3. The module name contains "And" or "Manager" or "Helper" (symptom of mixed concerns)
18
+ 4. More than 3 constructor/initialization parameters from different domains
19
+ 5. The module is modified in nearly every feature branch (high churn across unrelated features)
20
+
21
+ ### Severity Guide
22
+
23
+ | Severity | When to assign |
24
+ |----------|---------------|
25
+ | **HIGH** | Module mixes I/O (network, filesystem, database) with core business rules. Changes to infrastructure force changes to domain logic. |
26
+ | **MEDIUM** | Module handles 2-3 related but distinct concerns (e.g., validation + transformation). Concerns could be separated but aren't yet causing bugs. |
27
+ | **LOW** | Module has a slightly broad scope but concerns are closely related. Separation would be premature. |
28
+
29
+ ### Code Examples
30
+
31
+ **Violation:**
32
+ ```
33
+ class OrderService {
34
+ validateOrder(order) { ... } // business rule
35
+ calculateTotal(order) { ... } // business rule
36
+ saveToDatabase(order) { ... } // persistence
37
+ sendConfirmationEmail(order) { ... } // notification
38
+ generatePdfInvoice(order) { ... } // rendering
39
+ }
40
+ ```
41
+
42
+ **Healthy alternative:**
43
+ ```
44
+ class OrderValidator {
45
+ validate(order) { ... }
46
+ }
47
+
48
+ class OrderCalculator {
49
+ calculateTotal(order) { ... }
50
+ }
51
+
52
+ class OrderRepository {
53
+ save(order) { ... }
54
+ }
55
+
56
+ class OrderNotifier {
57
+ sendConfirmation(order) { ... }
58
+ }
59
+ ```
60
+
61
+ ### Detection Heuristics
62
+
63
+ - Count the number of distinct "domains" imported by a module (e.g., database, HTTP, email, file system). More than 2 suggests SRP violation.
64
+ - Check if removing one public method would make half the imports unnecessary. If so, the method belongs elsewhere.
65
+ - Look for methods that could be tested independently with completely different mock setups — they likely belong in different modules.
66
+
67
+ ---
68
+
69
+ ## O — Open/Closed Principle (OCP)
70
+
71
+ ### Definition
72
+
73
+ Software entities should be open for extension but closed for modification. You should be able to add new behavior without changing existing, tested code.
74
+
75
+ ### Violation Signals
76
+
77
+ 1. Adding a new variant requires modifying an existing `switch` or `if/else` chain
78
+ 2. A function has a growing list of type-check branches (`if (type === "A") ... else if (type === "B") ...`)
79
+ 3. Feature additions consistently require editing the same core file
80
+ 4. Configuration objects grow new boolean flags for each new feature
81
+ 5. Functions accept a `type` or `kind` string and branch on its value
82
+
83
+ ### Severity Guide
84
+
85
+ | Severity | When to assign |
86
+ |----------|---------------|
87
+ | **HIGH** | Every new feature requires modifying a critical-path module (e.g., event dispatcher, router). Risk of regression in existing behavior with each change. |
88
+ | **MEDIUM** | A switch/case or if/else chain has 5+ branches and is growing. Extension is possible but requires touching stable code. |
89
+ | **LOW** | A conditional has 2-3 branches in a non-critical path. The branching is manageable and unlikely to grow further. |
90
+
91
+ ### Code Examples
92
+
93
+ **Violation:**
94
+ ```
95
+ function calculateDiscount(customer) {
96
+ if (customer.type === "regular") {
97
+ return 0.05
98
+ } else if (customer.type === "premium") {
99
+ return 0.10
100
+ } else if (customer.type === "vip") {
101
+ return 0.20
102
+ }
103
+ // Adding a new customer type means editing this function
104
+ }
105
+ ```
106
+
107
+ **Healthy alternative:**
108
+ ```
109
+ // Strategy pattern — new types are added without modifying existing code
110
+ const discountStrategies = {
111
+ regular: () => 0.05,
112
+ premium: () => 0.10,
113
+ vip: () => 0.20,
114
+ }
115
+
116
+ function calculateDiscount(customer) {
117
+ const strategy = discountStrategies[customer.type]
118
+ return strategy ? strategy() : 0
119
+ }
120
+ // New types: just add an entry to the map
121
+ ```
122
+
123
+ ### Detection Heuristics
124
+
125
+ - Search for `switch` statements on a `type` or `kind` field — count the branches. Five or more suggests OCP violation.
126
+ - Check git history: if the same file is modified in >50% of feature branches, it may be closed to extension.
127
+ - Look for "registry" or "map" patterns that could replace branching logic.
128
+
129
+ ---
130
+
131
+ ## L — Liskov Substitution Principle (LSP)
132
+
133
+ ### Definition
134
+
135
+ Subtypes must be substitutable for their base types without altering the correctness of the program. If a function works with a base type, it must work identically with any derived type.
136
+
137
+ ### Violation Signals
138
+
139
+ 1. A subclass throws an exception for a method the base class supports
140
+ 2. A subclass overrides a method to do nothing (empty override or no-op)
141
+ 3. A function checks `instanceof` or the concrete type before calling a method
142
+ 4. A subclass narrows the accepted input range or widens the output range beyond the base contract
143
+ 5. Documentation says "do not call X on this subtype" — a direct substitutability violation
144
+
145
+ ### Severity Guide
146
+
147
+ | Severity | When to assign |
148
+ |----------|---------------|
149
+ | **HIGH** | Substituting the subtype causes runtime errors, data corruption, or silently wrong results. Code contains `instanceof` guards to work around the violation. |
150
+ | **MEDIUM** | Subtype behaves differently in edge cases that callers may not handle. No runtime error, but correctness depends on knowing the concrete type. |
151
+ | **LOW** | Subtype overrides behavior in a benign way (e.g., logging or metrics) that does not affect correctness. |
152
+
153
+ ### Code Examples
154
+
155
+ **Violation:**
156
+ ```
157
+ class Bird {
158
+ fly() { return "flying" }
159
+ }
160
+
161
+ class Penguin extends Bird {
162
+ fly() { throw new Error("Penguins cannot fly") }
163
+ // Violates LSP: callers expecting Bird.fly() to work will crash
164
+ }
165
+
166
+ function makeBirdFly(bird) {
167
+ // Forced to add a type check — LSP violation symptom
168
+ if (bird instanceof Penguin) {
169
+ return bird.swim()
170
+ }
171
+ return bird.fly()
172
+ }
173
+ ```
174
+
175
+ **Healthy alternative:**
176
+ ```
177
+ interface Movable {
178
+ move(): string
179
+ }
180
+
181
+ class Sparrow implements Movable {
182
+ move() { return "flying" }
183
+ }
184
+
185
+ class Penguin implements Movable {
186
+ move() { return "swimming" }
187
+ }
188
+
189
+ function makeAnimalMove(animal: Movable) {
190
+ return animal.move() // Works for all implementations
191
+ }
192
+ ```
193
+
194
+ ### Detection Heuristics
195
+
196
+ - Search for `instanceof` checks in functions that accept a base type — this is the classic LSP smell.
197
+ - Look for empty method overrides or methods that throw `NotImplementedError` / `UnsupportedOperationError`.
198
+ - Check if any subclass method has a comment like "not applicable" or "unused".
199
+ - Look for type narrowing (`as ConcreteType`) immediately after receiving a base-typed parameter.
200
+
201
+ ---
202
+
203
+ ## I — Interface Segregation Principle (ISP)
204
+
205
+ ### Definition
206
+
207
+ No client should be forced to depend on methods it does not use. Interfaces should be small and focused so that implementing classes are not burdened with irrelevant obligations.
208
+
209
+ ### Violation Signals
210
+
211
+ 1. An interface has more than 7-8 methods (likely too broad)
212
+ 2. Implementing classes leave methods as no-ops or throw "not supported"
213
+ 3. A single interface is imported by clients that only use a subset of its methods
214
+ 4. Interface changes force updates in modules that don't use the changed method
215
+ 5. Parameters typed as a broad interface when only 1-2 properties are accessed
216
+
217
+ ### Severity Guide
218
+
219
+ | Severity | When to assign |
220
+ |----------|---------------|
221
+ | **HIGH** | A broad interface forces implementors to provide dangerous no-op stubs for critical operations (e.g., `delete()` that silently does nothing). Clients may call methods that appear supported but aren't. |
222
+ | **MEDIUM** | Interface is large (8+ methods) and implementors stub out 2-3 methods. No safety risk but increases maintenance burden. |
223
+ | **LOW** | Interface is slightly broad but all implementors genuinely use most methods. Splitting would add complexity without clear benefit. |
224
+
225
+ ### Code Examples
226
+
227
+ **Violation:**
228
+ ```
229
+ interface Worker {
230
+ work(): void
231
+ eat(): void
232
+ sleep(): void
233
+ attendMeeting(): void
234
+ writeReport(): void
235
+ }
236
+
237
+ class Robot implements Worker {
238
+ work() { /* ... */ }
239
+ eat() { /* no-op — robots don't eat */ }
240
+ sleep() { /* no-op */ }
241
+ attendMeeting() { /* no-op */ }
242
+ writeReport() { /* ... */ }
243
+ }
244
+ ```
245
+
246
+ **Healthy alternative:**
247
+ ```
248
+ interface Workable {
249
+ work(): void
250
+ }
251
+
252
+ interface Reportable {
253
+ writeReport(): void
254
+ }
255
+
256
+ interface HumanNeeds {
257
+ eat(): void
258
+ sleep(): void
259
+ }
260
+
261
+ class Robot implements Workable, Reportable {
262
+ work() { /* ... */ }
263
+ writeReport() { /* ... */ }
264
+ }
265
+
266
+ class HumanWorker implements Workable, Reportable, HumanNeeds {
267
+ work() { /* ... */ }
268
+ writeReport() { /* ... */ }
269
+ eat() { /* ... */ }
270
+ sleep() { /* ... */ }
271
+ }
272
+ ```
273
+
274
+ ### Detection Heuristics
275
+
276
+ - Count methods per interface. More than 7 is a signal worth investigating.
277
+ - Search for empty method bodies or `throw new Error("Not implemented")` in classes implementing an interface.
278
+ - Check if any implementation only uses <50% of the interface methods meaningfully.
279
+ - Look for function parameters typed with a broad interface where only 1-2 fields/methods are accessed in the body.
280
+
281
+ ---
282
+
283
+ ## D — Dependency Inversion Principle (DIP)
284
+
285
+ ### Definition
286
+
287
+ High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details — details should depend on abstractions.
288
+
289
+ ### Violation Signals
290
+
291
+ 1. A domain/business-logic module directly imports a database driver, HTTP client, or file system module
292
+ 2. Constructor creates its own dependencies instead of receiving them (no dependency injection)
293
+ 3. Module-level `import` of a concrete implementation where an interface/type would suffice
294
+ 4. A core module imports from an `infrastructure/`, `adapters/`, or `io/` directory
295
+ 5. Changing a database library requires editing business logic files
296
+
297
+ ### Severity Guide
298
+
299
+ | Severity | When to assign |
300
+ |----------|---------------|
301
+ | **HIGH** | Core business logic directly instantiates or imports infrastructure (database, network, filesystem). Impossible to test without real infrastructure or heavy mocking. |
302
+ | **MEDIUM** | A module depends on a concrete implementation but the dependency is injected (not self-created). The direction is wrong but testability is preserved. |
303
+ | **LOW** | A utility module depends on a specific library for convenience. The dependency is isolated and easily swappable. |
304
+
305
+ ### Code Examples
306
+
307
+ **Violation:**
308
+ ```
309
+ // Domain module directly importing infrastructure
310
+ import { Pool } from 'pg'
311
+ import { S3Client } from '@aws-sdk/client-s3'
312
+
313
+ class OrderService {
314
+ private db = new Pool({ connectionString: process.env.DB_URL })
315
+ private s3 = new S3Client({ region: 'us-east-1' })
316
+
317
+ async createOrder(data) {
318
+ // Business logic tightly coupled to Postgres and S3
319
+ await this.db.query('INSERT INTO orders ...', [data])
320
+ await this.s3.send(new PutObjectCommand({ ... }))
321
+ }
322
+ }
323
+ ```
324
+
325
+ **Healthy alternative:**
326
+ ```
327
+ // Domain depends on abstractions
328
+ interface OrderRepository {
329
+ save(order: Order): Promise<void>
330
+ }
331
+
332
+ interface FileStorage {
333
+ upload(key: string, data: Buffer): Promise<void>
334
+ }
335
+
336
+ class OrderService {
337
+ constructor(
338
+ private repo: OrderRepository,
339
+ private storage: FileStorage,
340
+ ) {}
341
+
342
+ async createOrder(data) {
343
+ const order = Order.create(data)
344
+ await this.repo.save(order)
345
+ await this.storage.upload(order.invoiceKey, order.toPdf())
346
+ }
347
+ }
348
+
349
+ // Infrastructure implements the abstractions
350
+ class PostgresOrderRepository implements OrderRepository { ... }
351
+ class S3FileStorage implements FileStorage { ... }
352
+ ```
353
+
354
+ ### Detection Heuristics
355
+
356
+ - Check import paths in domain/core modules: do they import from `infrastructure/`, `adapters/`, `db/`, `io/`, or driver-specific packages?
357
+ - Search for `new` keyword in domain modules creating infrastructure objects (database connections, HTTP clients, cache clients).
358
+ - Look for `process.env` access in domain modules — environment configuration is an infrastructure concern.
359
+ - Check if the composition root (entry point / DI container) is the only place where concrete implementations are wired to abstractions.