82 lines
4.1 KiB
Markdown
82 lines
4.1 KiB
Markdown
# ADR-0004: Package modules by domain, not by stage
|
|
|
|
**Status:** Accepted — its asset-co-location rule is superseded by [ADR-0011](0011-centralize-llm-assets.md); its domain-first module packaging decision remains accepted.
|
|
**Date:** 2026-07-13
|
|
|
|
## Context
|
|
|
|
Module packages can be grouped first by pipeline stage, such as
|
|
`modules/chunk/dnd/scenes`, or first by domain, such as
|
|
`modules/dnd/chunk/scenes`. A domain's extract, merge, normalize, validation,
|
|
schema, prompt, and artifact-codec implementations collaborate around the same
|
|
artifact types and are likely to evolve together.
|
|
|
|
Go package dependencies also constrain registration. If shared types live in a
|
|
domain root package, that package cannot import child implementation packages
|
|
to register them because the children already import the root types.
|
|
|
|
## Decision
|
|
|
|
Production extensions are grouped by domain under:
|
|
|
|
```text
|
|
internal/modules/<domain>/<stage>/<name>
|
|
```
|
|
|
|
Shared artifact types live at the domain root, for example
|
|
`internal/modules/dnd/types.go`. Domain-specific validators, prompt fragments,
|
|
schemas, reference helpers, and codecs also live within that domain tree.
|
|
|
|
Each domain exposes one production registration entry point from a sibling
|
|
registrar package, for example `internal/modules/dnd/register`. The registrar
|
|
may import the domain root and its child implementations; the domain root does
|
|
not import its registrar or child packages. This keeps shared types available
|
|
as `dnd.SpellList` without creating a Go import cycle.
|
|
|
|
The `generic` tree is a peer extension family for reusable implementations that
|
|
contain no concrete source-format or artifact-domain knowledge. Source-format
|
|
and output-format families, such as Seriatim and JSON output, follow the same
|
|
domain-first organization even when they do not define a type in the
|
|
[domain artifact zone](0003-typed-interfaces-with-two-zone-data-model.md#domain-artifact-zone).
|
|
|
|
Concrete domain implementation packages do not import another concrete domain.
|
|
Generic extension packages never import concrete domains. A domain registrar
|
|
may import domain-neutral generic extension packages to instantiate a reusable
|
|
strategy for that domain's artifact type; the generic implementation remains
|
|
unaware of the concrete type's domain semantics. Reuse needed directly by a
|
|
domain implementation lives in a domain-neutral framework or helper package,
|
|
not in a peer extension package.
|
|
|
|
The application composition root may import multiple registrar packages, and
|
|
black-box integration tests may compose multiple domains. Other cross-domain
|
|
reuse occurs through engine contracts and composition-time registration rather
|
|
than concrete peer-domain imports.
|
|
|
|
A domain registrar owns registration of that domain's modules, validators,
|
|
default validator chains, artifact codecs, schemas, and prompt assets. It does
|
|
not take ownership of application execution or process behavior.
|
|
|
|
## Alternatives considered
|
|
|
|
- Group modules by stage. This keeps interchangeable strategies side by side,
|
|
but scatters a domain's shared artifact model and collaborating extensions
|
|
across the repository. It is preferable when generic strategy libraries
|
|
dominate or when the project is primarily a stage-extension framework rather
|
|
than an application composed from domain suites.
|
|
- Put both shared types and `Register` in the domain root. This gives the
|
|
shortest import path but creates an import cycle once child implementations
|
|
import the root artifact types.
|
|
|
|
## Consequences
|
|
|
|
The repository layout makes supported domains immediately visible, and adding
|
|
or extracting a domain affects one cohesive subtree. The CLI composition root
|
|
depends on a small set of domain registrars instead of every leaf package.
|
|
|
|
Package moves must preserve user-visible module and validator keys unless a
|
|
separate compatibility decision changes them. Shared behavior that cannot be
|
|
expressed through framework contracts may need to move into a domain-neutral
|
|
framework package rather than creating a concrete peer-domain import. Registrar
|
|
packages become explicit composition points for instantiating generic typed
|
|
strategies, in addition to registering domain-owned implementations.
|