A namespace rule enforced in CI is a boundary that holds

An architectural boundary that exists only in a diagram is a boundary that has already been crossed, and the enforcement is a morning’s work.

# deptrac.yaml
layers:
  - name: Billing
    collectors: [{ type: className, regex: ^App\Billing\.* }]
  - name: Ordering
    collectors: [{ type: className, regex: ^App\Ordering\.* }]
  - name: Shared
    collectors: [{ type: className, regex: ^App\Shared\.* }]

ruleset:
  Billing: [Shared]
  Ordering: [Shared]
  Shared: ~

A violation becomes a failed build rather than a review comment somebody may or may not make, which is the entire difference. Starting with a baseline of existing violations and forbidding new ones is the tractable path on a codebase that has never had this — the same ratchet as a static analysis baseline. A grep-based check in a shell script covers most of it if adding a dependency is unwelcome.