ADR-034: Nx Module Boundary Enforcement

Status: Proposed
Version: 1.0
Date: 2025-12-28
Supersedes: N/A
Related ADRs: ADR-033 (Kernel-Shell Architecture), ADR-030 (VibesPro™ Foundation)
Related PRDs: PRD-023


Context

With the Kernel-Shell architecture (ADR-033), SEA™ contains multiple bounded contexts in a single Nx workspace:

Without enforcement, developers will inevitably create cross-boundary imports:

Symptom observed in similar projects: After 1-2 months, bounded contexts become entangled, and the architecture diagram no longer matches reality.

Decision

We adopt Nx module boundary enforcement using tags and dependency rules, validated in CI.

Tag Taxonomy

1
2
scope:{context}     - Which bounded context owns the lib
type:{layer}        - What architectural layer the lib belongs to
Tag Category Values Purpose
scope:sea SEA™ kernel libraries Core domain ownership
scope:vibespro VibesPro™ libraries VibesPro™ domain ownership
scope:shared Cross-cutting utilities Shared by all contexts
type:domain Pure domain logic No I/O, no dependencies
type:app Use-cases/orchestration Business workflows
type:ports Interfaces/protocols Abstraction boundaries
type:adapters Infrastructure implementations I/O, DB, HTTP, queues
type:public Public API facades Only cross-context entry point
type:shell Application composition Config + wiring only

Dependency Rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
  "@nx/enforce-module-boundaries": [
    "error",
    {
      "depConstraints": [
        {
          "sourceTag": "scope:sea",
          "onlyDependOnLibsWithTags": [
            "scope:sea",
            "scope:shared",
            "type:public"
          ]
        },
        {
          "sourceTag": "scope:vibespro",
          "onlyDependOnLibsWithTags": ["scope:vibespro", "scope:shared"]
        },
        {
          "sourceTag": "type:domain",
          "onlyDependOnLibsWithTags": ["type:domain", "scope:shared"]
        },
        {
          "sourceTag": "type:app",
          "onlyDependOnLibsWithTags": [
            "type:domain",
            "type:ports",
            "scope:shared"
          ]
        },
        {
          "sourceTag": "type:ports",
          "onlyDependOnLibsWithTags": ["type:domain", "scope:shared"]
        },
        {
          "sourceTag": "type:adapters",
          "onlyDependOnLibsWithTags": [
            "type:domain",
            "type:app",
            "type:ports",
            "scope:shared"
          ]
        },
        {
          "sourceTag": "type:shell",
          "onlyDependOnLibsWithTags": [
            "type:app",
            "type:adapters",
            "type:public",
            "scope:shared"
          ]
        }
      ]
    }
  ]
}

Key Rules Explained

Rule Meaning Prevents
scope:seascope:vibespro only via type:public SEA™ integrates with VibesPro™ through public facade only Tight coupling to VibesPro™ internals
type:domain → never type:adapters Domain has no infrastructure dependencies I/O leaking into business logic
type:adapters → inward only Adapters implement ports, depend on domain Adapter logic in domain
Shells cannot import domain directly Shells compose via app layer Domain logic in composition

Example Library Configurations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// libs/sea/domain/project.json
{
  "tags": ["scope:sea", "type:domain"]
}

// libs/vibespro/public-api/project.json
{
  "tags": ["scope:vibespro", "type:public"]
}

// libs/sea/adapters/postgres/project.json
{
  "tags": ["scope:sea", "type:adapters"]
}

// apps/sea-api/project.json
{
  "tags": ["scope:sea", "type:shell"]
}

Rationale

  1. Fail Fast: Illegal imports fail at lint time, not runtime
  2. Self-Documenting: Tags describe architecture in code
  3. Forced Discipline: Can’t “just this once” bypass boundaries
  4. Audit Trail: nx graph shows actual dependencies
  5. Isomorphic: Tags map directly to DDD layers

Constraints

MUST

MUST NOT

Isomorphic Guarantees

Spec Concept Nx Representation Verification
Bounded Context scope:* tag Graph shows isolated clusters
Domain Layer type:domain Zero adapter imports
Ports type:ports interface-only files
Adapters type:adapters Implementation files
Public Contract type:public Only cross-scope entry

System Invariants

INV-ID Invariant Type Enforcement
INV-034 All libs have scope + type tags Structural CI lint check
INV-035 Domain never imports adapters Structural Nx boundary rule
INV-036 Cross-scope only via public Structural Nx boundary rule
INV-037 Graph remains acyclic Structural nx graph validation

Quality Attributes

Attribute Target Rationale
Build Isolation Each lib builds independently Parallel CI
Testability Domain testable without infra Fast unit tests
Maintainability Clear ownership per scope Team autonomy
Evolvability Can split BC to separate repo Future microservices

Bounded Contexts Impacted

All bounded contexts and libraries in the workspace.

Consequences

Positive

Negative

Mitigations


Observable Success