Skip to content

Tach boundaries

Tach is the backend architecture gate.

Terminal window
uv run tach check --dependencies --interfaces --exact
uv run tach check-external
  • Layer order is domain <- application <- adapters/interfaces <- orchestration.
  • Domain cannot depend on application, adapters, interfaces, orchestration, or platform.
  • Application cannot depend on adapters, interfaces, orchestration, or platform.
  • Adapters and interfaces are peers; neither imports the other. Orchestration owns their composition.
  • Bounded contexts cannot import each other directly.
  • Feature slices cannot import sibling feature internals. Collaboration uses a public contract or consumer-owned port that the architecture graph declares.
  • Cross-bounded-context smoke tests live under tests/integration, not under src/sanmopia_modernization/application; otherwise Tach treats them as application code and blocks adapter or sibling-context imports.
  • Application bounded-context slices must use the same ubiquitous context name as domain slices.
  • Adapter bounded-context slices must use the same ubiquitous context and feature names as their domain/application slices.
  • Business interface modules use interfaces/<bounded_context>/features/<feature>; thin protocol hosts such as interfaces/mcp may dispatch only to those owned slices.
  • CQRS is optional inside an application feature. Direct commands/ and queries/ folders are allowed only when populated; cqrs/ wrappers and global command/query folders are rejected.
  • Domain cannot import FastAPI, Pydantic, SQLAlchemy, Supabase SDK, or runtime adapters.
  • Pydantic roots live in sanmopia_modernization.pydantic_model, outside domain/application/interface subtrees, so Tach does not create parent-module back edges.
  • Application Pydantic DTOs must derive through SanmopiaPydanticModel -> ApplicationContractModel -> FeatureContractModel -> concrete payload. Concrete application payloads must not inherit directly from BaseModel.
  • Interface Pydantic models must derive through SanmopiaPydanticModel -> shared interface root -> slice-specific HTTP/Restate/API base -> concrete model. Concrete HTTP payloads, workflow payloads, and responses must not inherit directly from BaseModel.
  • Generated Python contracts follow the same shape: only the package root model may inherit from pydantic.BaseModel; concrete request/response and nested payload models must pass through semantic intermediate models such as workflow context, caregiver snapshot, branch actor snapshot, catalog option, or workflow envelope.

Backend ee7b9b8 added root Tach prohibitions plus an AST structural test for the fixed layer direction. It also removed the observed reservation-booking adapter-to-interface imports. The graph still has undeclared feature ownership, flat interfaces, and the root runtime composition module. Until those are migrated and declared, report the architecture as partial even when Tach is green.