# Code Conventions Ratchet

Enforcement mechanism for two 2026-07-16 monorepo-architecture-audit findings
that could not be closed by a one-shot migration:

- **R-11 (DRY / shared-lib adoption):** the canonical platform layer exists
  (`@oshun/logging`, `@oshun/database`, `@oshun/cache`, `@oshun/config`), but
  hundreds of files predate it and re-implement or bypass it. Migrating them is
  per-domain feature work; what must not happen is the debt **growing**.
- **F-1 (test-file suffix):** the workspace convention is colocated `*.spec.ts`
  (the plurality at audit time). ~12.6k `*.test.ts` files are grandfathered;
  renaming them wholesale would churn thousands of vitest configs and diffs for
  no behavioral gain.

## Mechanism

`tools/conventions/check-conventions-ratchet.mjs` runs in the `ci.yml` quality
job. It compares the current tree against the committed baseline
(`tools/conventions/conventions-ratchet-baseline.json`):

| Rule                          | Kind     | Fails when                                                                 | Fix                                |
| ----------------------------- | -------- | -------------------------------------------------------------------------- | ---------------------------------- |
| `raw-pino-import`             | file set | a file not in the baseline imports `pino` directly (outside `libs/shared`) | import from `@oshun/logging`       |
| `raw-pg-pool`                 | file set | a new file constructs `new Pool(` from `pg`                                | use `@oshun/database`              |
| `raw-ioredis-client`          | file set | a new file constructs `new Redis(` from `ioredis`                          | use `@oshun/cache`                 |
| `direct-process-env`          | file set | a new production file reads `process.env` directly                         | use `@oshun/config` (typed schema) |
| `local-circuit-breaker-class` | file set | a new file defines its own `CircuitBreaker` class                          | use `@oshun/resilience`            |
| `local-sleep-def`             | file set | a new file defines its own `sleep`/`delay`                                 | use `@oshun/resilience`            |
| `test-dot-test-suffix`        | count    | the tracked `*.test.ts(x)` count under `libs/`+`apps/` grows               | name new test files `*.spec.ts`    |

File-set rules are strict set-ratchets: a **new** file entering the set fails CI
even if another file left it. Existing (grandfathered) files pass; migrating one
off the raw dependency is always welcome.

## Tightening

When a change removes grandfathered occurrences, tighten the baseline in the
same PR:

```bash
node tools/conventions/check-conventions-ratchet.mjs --update-baseline
git add tools/conventions/conventions-ratchet-baseline.json
```

The checker prints a reminder whenever counts drop below the baseline.

## Scope notes

- Tests (`*.spec.ts`, `*.test.ts`, `__tests__/`), `*.config.ts`, and e2e trees
  are excluded from the shared-lib rules — test doubles and config loaders at
  dependency boundaries are legitimate.
- `libs/shared/**` is excluded: the shared layer is where the raw dependencies
  are supposed to live.
- Related policy: `docs/conventions/server-frameworks-and-deps.md` (sanctioned
  frameworks and overlapping-dependency policy, audit R-17).
