# Config-driven clean structure

Platform implementation should separate reusable engine code from
source-specific configuration: **the engine decides *how* work is done; source
config decides *what* gets done.**

Onboarding a source should usually mean adding or changing Git-authored source
YAML and reviewed generated contracts — not writing one-off scripts, hidden
scratch metadata, or source-specific engine code.

## Good

A unit keeps a reusable execution engine separate from per-source config, with
genuinely shared concerns factored into `common/`:

```
common/                  # shared, source-agnostic modules
<stage>/
  <unit>/
    setup/               # setup this unit owns
      grunt/             # terragrunt modules
      scripts/           # or plain scripts
    engine/              # reusable execution logic — no source values
    sources/
      <source>/
        source.yaml      # the source definition
        contracts/       # authored/generated contracts, beside the stage that produces them
        secrets.env      # ignored
        runs/            # ignored
```

- **Engine code carries no source facts.** Bucket names, schemas, classification
  rules, watermark columns — all come from `source.yaml`, so onboarding the next
  source never means editing the engine.
- **Shared, source-agnostic concerns live in `common/`,** not copied into each
  unit.
- **Setup a unit owns lives under that unit's `setup/`,** reading its values from
  `source.yaml` so declared things (like retention) are actually enforced, not
  dropped. A repo-root `setup/` is reserved for cross-cutting orchestration that
  spans units — never per-source SQL or values.

## Bad

One-off, per-source code and scratch SQL:

```
<unit>_<source>_execution_logic.py
sql_1_<unit>_<source>.sql
```

The subtler, more common violation: the file layout looks clean, but a source's
*values or logic* leak into reusable code, so onboarding the next source means
editing the engine.

```python
# engine/classification.py — one source's rules baked into the engine
PATTERNS = {"pii": r"email|phone|...", "financial": r"amount|revenue|..."}
if table.name.startswith("_System_"): ...   # one source's naming convention
```

The fix is not a new file — it is moving the *data* to `source.yaml` and keeping
the engine generic (`classify_table(table, rules)`, watermark candidates read
from config).

## Relation to ADRs

This principle is the implementation expression of
[ADR 0009](../adr/0009-contract-engine-collates-contracts-and-asset-metadata.md):
asset-local fragments and generated contracts live beside the stage that
produces them, and a contract engine collates them into validated authored
contracts. *Which* stage owns *which* contract is a separate decision
([ADR 0015](../adr/0015-contracts-have-a-kind-and-the-producer-owns-the-contract.md),
[0016](../adr/0016-load-materializes-only-the-raw-layer.md),
[0018](../adr/0018-externally-managed-sources-load-incrementally-with-inferred-drift-gated-contracts.md)).
