ADR 029 Layer-first Context Feature Slices
Context
Section titled “Context”Feature-first placement makes the feature folder look like the highest architecture authority. It also obscures dependency direction, encourages feature-to-feature deep imports, and can turn CQRS into a false global layer.
The workspace owner explicitly selected DDD layers as the top-level authority on 2026-07-13. Bounded context and feature slice remain important, but they are nested inside each layer.
Decision
Section titled “Decision”Backend production code follows this order:
src/sanmopia_modernization/├── domain/<bounded_context>/features/<feature>/├── application/<bounded_context>/features/<feature>/├── adapters/<bounded_context>/features/<feature>/├── interfaces/<bounded_context>/features/<feature>/├── orchestration/<bounded_context>/features/<feature>/└── platform/Thin transport hosts such as interfaces/mcp may exist, but they only bind
protocol concerns to context-owned interface slices. They do not own business
use cases.
The dependency direction is fixed:
domain <- application <- adapters/interfaces <- orchestrationdomainowns business vocabulary, facts, invariants, policies, and ports whose ownership is genuinely domain-level.applicationcoordinates use cases and declares consumer-owned ports and public feature contracts.adaptersimplement ports. They do not construct or import interface APIs.interfacesauthenticate, authorize, validate transport contracts, and call application entry points. They do not import adapter implementations.orchestrationis the composition boundary for adapters, interfaces, workflows, and process startup.platformcontains generic runtime, configuration, observability, and provider facilities. It owns no business rule and is never imported bydomainorapplication.
Sibling features and bounded contexts cannot deep-import each other’s implementation modules. Collaboration requires an explicitly owned public contract or port. A direct import is not accepted merely because Tach currently permits it.
Feature-local CQRS
Section titled “Feature-local CQRS”CQRS is not a top-level layer and is not mandatory. It is optional structure inside an application feature:
application/└── knowledge/ └── features/ └── report_catalog/ ├── commands/ │ └── publish_report.py └── queries/ └── search_reports.py- A read-only feature has
queries/only. - A write-only feature has
commands/only. - A mixed feature has both only when separate models clarify the use case.
- Empty
commands/orqueries/directories are forbidden. - A
cqrs/wrapper directory is forbidden. - Global
application/commandsandapplication/queriesdirectories are forbidden. - Domain aggregates and policies remain in the domain feature; executable use cases and read projections belong to the application feature.
Enforcement
Section titled “Enforcement”Architecture tests and Tach declarations must converge on this decision. A green gate is insufficient while production modules remain outside its graph. The target gates must detect:
- reverse layer imports;
adapters <-> interfacesimports;- context or feature deep imports without a public contract/port;
- flat business interface modules outside context/feature slices;
- business composition in adapters or interfaces;
- root runtime/composition files outside
orchestration; - obsolete
cqrs/wrappers, global CQRS directories, and empty command/query directories.
Current Evidence
Section titled “Current Evidence”The 2026-07-13 audit confirmed that domain/application/adapters roots already exist and no top-level feature root exists. Domain framework purity and the currently declared Tach boundaries pass.
Backend commit
ee7b9b8
closed the first enforceable slice:
- moved reservation-booking API composition from adapters to
orchestration/reservation_operations/features/reservation_booking; - added structural layer-direction tests and Tach root prohibitions for adapters, interfaces, and orchestration;
- moved settlement commands and queries out of
cqrs/into populated, use-case-named application subpackages; - moved the flat document code predicate into the public
document_identitydomain feature contract.
Backend
8aeaf0a
then replaced four representative sibling-feature deep imports with public
domain, application, and adapter feature contracts. This is a bounded first
slice, not proof that every sibling import is compliant.
Backend
4e87532
moved the reservation-booking application slice to
application/reservation_operations/features/reservation_booking and kept the
authenticated mother submitter distinct from the operational branch actor.
Backend
b760b05
removed the flat adapters/reservation_booking package. Booking adapters now
live under adapters/reservation_operations/features/reservation_booking,
mother context has its own feature, and daily-report, service-calendar,
settlement, and caregiver-payout consumers live with their owning context or
orchestration slice. A structural architecture test forbids legacy Python
sources and verifies these migrated adapter slices. Feature-level Tach modules
pass exact, interface, dependency, and external checks.
Backend
a768cc2
removed the flat reservation-booking API, HTTP, and Restate interface modules.
They now live under
interfaces/reservation_operations/features/reservation_booking and expose
production contracts only through the feature package. A structural test
rejects legacy flat files and production deep imports. The moved tests remain
colocated, shared HTTP fixtures use an explicit test-support module, and Tach
passes with a feature-level interface declaration.
Backend
438e8dc
moved the branch-office profile API, HTTP boundary, and colocated tests into
interfaces/branch_operations/features/office_profile. Production composition
imports only the public feature package, the architecture gate rejects the old
flat modules and deep production imports, and Tach owns the new feature module.
The same verification run exposed two pre-existing branch settlement export
tests whose fixtures omitted mandatory dataset revision lineage. Backend
f0f32bb
restored those fixture facts without weakening the production model. The
combined diff passed 528 Tach-selected tests, all exact/interface/dependency/
external checks, and a confidence-100 Vulture scan with zero candidates.
Backend
52def80
then moved the branch service-area policy API, HTTP boundary, and colocated tests
into interfaces/branch_operations/features/service_area_policy. Route and
contract behavior stayed unchanged. The public feature package, flat-module and
deep-import guards, and Tach feature declaration passed 514 affected tests;
Tach reported zero diagnostics and the focused confidence-100 Vulture scan
reported zero candidates.
Backend
c9b06d7
adds a new read-only reservation-operation worklist as an ADR-029 exemplar:
- the use case exists only under
application/reservation_operations/features/reservation_operation_worklist/queries; it has no empty or unnecessarycommands/package; - Supabase and SpiceDB implementations live under the matching adapter feature;
- FastAPI/API models and colocated tests live under the matching interface feature;
- production composition imports the public feature packages; and
- Tach declares separate application, adapter, and interface modules with inward-only dependencies and explicit adapter/interface prohibitions.
The current Tach inventory has 178 modules. Exact dependency/interface and
external checks reported zero diagnostics, Tach selected 33 affected tests and
skipped 4,921 unaffected tests, and the focused confidence-100 Vulture MCP scan
reported zero candidates. The stage-data browser proof belongs to the feature
scenario, not to this architecture decision; it does not upgrade ADR-029 from
partial while the root composition and flat interface gaps below remain.
Implementation is still partial because:
interfacesremains largely flat outside the reservation-booking pilot;- top-level
orchestrationnow has one pilot slice, whileplatformplacement is incomplete; - the runtime composition root remains in a large top-level
main.py; - some application/adapter contexts do not yet use
features/<feature>; - current Tach declarations do not cover the full production graph or all feature-level contract rules.
Migration Order
Section titled “Migration Order”- Expand the initial architecture tests and Tach root rules to the complete production graph and public feature contracts.
- Replace the remaining flat reservation authorization helper with an application permission port and context-owned interface adapters without assigning shared authorization to the booking feature.
- Extract public contracts/ports for verified cross-feature collaborations.
- Move flat interfaces and root runtime composition incrementally, with colocated tests and stage smoke proof per slice.
Consequences
Section titled “Consequences”Architecture changes must start from layer ownership, then context, then feature. New code cannot copy a known partial path as precedent. Existing paths remain migration debt until code movement, import gates, tests, and runtime proof all agree.
Revision History
Section titled “Revision History”- 2026-07-13: accepted from the workspace owner’s explicit architecture direction; supersedes ADR 002. Implementation remains partial pending full graph enforcement and incremental migration.
- 2026-07-13: backend
ee7b9b8added the first layer-direction gate, moved reservation API composition to orchestration, removed the settlementcqrs/wrapper, and promoted a flat domain helper into a public feature contract. - 2026-07-13: backend
8aeaf0apublished representative cross-feature contracts for payment purpose, payment/compensation projection inputs, service-catalog storage facts, and service-balance closeout records. - 2026-07-13: backend
4e87532moved the reservation-booking application slice into the reservation-operations context and separated authenticated submitter evidence from the branch operational actor. - 2026-07-13: backend
b760b05removed the flat reservation-booking adapter, distributed cross-feature consumers to their owners, and added migrated adapter structure plus feature-level Tach enforcement. - 2026-07-13: backend
a768cc2moved reservation-booking API, HTTP, Restate, tests, and test support into the reservation-operations interface slice and enforced public production imports. - 2026-07-13: backend
438e8dcmoved branch-office profile API, HTTP, and tests into the branch-operations interface slice;f0f32bbrepaired stale export test lineage found by the affected-test gate. - 2026-07-13: backend
52def80moved branch service-area API, HTTP, and tests into the branch-operations interface slice without changing behavior. - 2026-07-13: backend
c9b06d7added the query-only reservation-operation worklist across application, adapter, and interface feature slices and passed Tach exact/external, 33 affected tests, and focused Vulture with zero findings.