# ADR-005: Architecture-fitness enforcement via deptrac

- **Status:** Accepted
- **Date:** 2026-05-17
- **Deciders:** Дмитрий

## Context

Map section A6 «Архитектура систем» had tools to *record* (adr-kit #36),
*visualize* (mermaid-skill #37) and *reference* (architecture-patterns #38)
architecture — but nothing enforced that the code keeps matching the layered
architecture (Controller → Service → Model …). `adr-judge` (adr-kit) enforces
only what is hand-written as a regex in an ADR `## Enforcement` block — too
narrow for dependency-direction rules.

## Decision

- Adopt **deptrac** (`deptrac/deptrac`, BSD-3, v4.x) — a declarative, zero-LLM
  static-analysis tool — as the layer-dependency gate, wired as lefthook
  pre-commit job 10.
- The layer model and ruleset live in `app/deptrac.yaml` (conservative — it
  enforces only inward/upward-violating directions: a Model must not depend on
  a Service, a Service must not depend on the Http layer, etc.).
- The first `deptrac analyse` reported **0 violations** — the codebase already
  conforms to the layer model (481 allowed cross-layer dependencies, 0
  violations) — so no baseline file is needed. If future drift is ever
  intentionally accepted, a `deptrac.baseline.yaml` can be generated then; for
  now the gate runs clean with no suppressions.

## Consequences

- Positive: layer drift is caught at commit time, deterministically, at zero
  LLM cost (the AK6 principle adr-kit was built under).
- Positive: deptrac's code-derived graph output doubles as a drift-proof
  C4-component diagram (`docs/architecture/c4-component-layers.md`).
- Risk: a too-strict ruleset produces noise — mitigated by the conservative
  ruleset (verified: 0 violations against the current codebase).
- Risk: deptrac is third-party — bus-factor; mitigated by composer-lock pinning.

## Enforcement

The layer rules live in `app/deptrac.yaml`, enforced by lefthook pre-commit
job 10 (`deptrac analyse`) — not by an `adr-judge` regex. This ADR therefore
carries no `adr-judge`-parsed Enforcement clause.

## Amendments

### 2026-05-29 — Mail ⟶ Service value objects allowed

After Stage 4 slepok routing protection rollout, billing introduced
`PreflightResult` (`app/Services/Billing/PreflightResult.php`) — a value
object representing a pre-flight check result, used by Mail templates
(`BalanceFrozenReminderMail`, `BalanceUnfrozenMail`, `BalanceFrozenMail`,
`BalanceFrozenFinalMail`) для рендера email с runtime данными.

Original ruleset: `Mail: [Model]` — blocked Mail ⟶ Service deps.
4 pre-existing violations accumulated, unnoticed until incident 2026-05-29
(`docs/incidents/2026-05-29-disk-full-pg-recovery.md`) forced first PHP commit.

**Decision:** Mail layer **может** depend на Service value objects (DTOs,
readonly result classes). Это template-rendering legitimate need: Mail
получает data DTO от Service и рендерит — no business logic, just data
projection.

Updated ruleset: `Mail: [Model, Service]`. Result: 0 violations.

**NB:** этот allowance не открывает Mail к active Service calls (e.g. invoking
`LedgerService::charge()` из template). Convention: Mail может только **read**
readonly Service DTOs, не вызывать mutating Service methods. Enforcement
этого convention pending — currently deptrac granularity layer-level only.
