fix(boundaries): allow feature-to-feature imports of public exports
turbo.json's boundary config already allows `feature -> feature`, and the cross-feature event system depends on it — a consumer must import the publisher's event contract from `@repo/<publisher>`. But the ESLint boundaries config, ADR-010, and AGENTS.md still declared `feature -> [core, tooling]`, contradicting turbo.json and the shipped code (marketing-pages imports @repo/auth). Align all three to turbo.json: a feature may import another feature's published public exports. Internals stay sealed by the `exports` map, and cross-feature behaviour still flows through IEventBus.
This commit is contained in:
@@ -38,6 +38,7 @@ ADR-006 mentioned three tags. This ADR refines the model to five, distinguishing
|
||||
- **tooling** — `packages/core-eslint`, `core-typescript`
|
||||
|
||||
Why `core-trpc` is `core-composition`:
|
||||
|
||||
- `core-trpc` imports `@repo/core-api` (the tRPC app router)
|
||||
- `core-api` imports feature routers from `@repo/<feature>/api`
|
||||
- Therefore, `core-trpc` transitively depends on features through the `AppRouter` type
|
||||
@@ -51,17 +52,40 @@ Why `core-trpc` is `core-composition`:
|
||||
{
|
||||
"boundaries": {
|
||||
"tags": {
|
||||
"app": { "dependencies": { "allow": ["app", "core", "core-composition", "feature", "tooling"] } },
|
||||
"core-composition": { "dependencies": { "allow": ["core", "core-composition", "feature", "tooling"] } },
|
||||
"core": { "dependencies": { "allow": ["core", "core-composition", "tooling"] } },
|
||||
"feature": { "dependencies": { "allow": ["core", "tooling"] } },
|
||||
"app": {
|
||||
"dependencies": {
|
||||
"allow": ["app", "core", "core-composition", "feature", "tooling"]
|
||||
}
|
||||
},
|
||||
"core-composition": {
|
||||
"dependencies": {
|
||||
"allow": ["core", "core-composition", "feature", "tooling"]
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"dependencies": { "allow": ["core", "core-composition", "tooling"] }
|
||||
},
|
||||
"feature": {
|
||||
"dependencies": { "allow": ["core", "feature", "tooling"] }
|
||||
},
|
||||
"tooling": { "dependencies": { "allow": ["tooling"] } }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Amendment (2026-05-21) — feature → feature.** The `feature` tag's allow-list
|
||||
> includes `feature`. A feature package may depend on another feature's
|
||||
> _published public exports_ — the `@repo/<feature>` contract barrel (types,
|
||||
> schemas, errors, event contracts). This is required by the cross-feature
|
||||
> event system (ADR-015): a consumer must import the publisher's event
|
||||
> contract. Two guardrails remain: each feature's `exports` map seals its
|
||||
> internals (only `.`, `./ui`, `./api`, `./cms`, `./di/bind-*` are reachable),
|
||||
> and cross-feature _behaviour_ still flows through `IEventBus` — a feature
|
||||
> never imports and invokes another feature's use cases directly.
|
||||
|
||||
2. **Per-package `turbo.json`** declares the package's tag:
|
||||
|
||||
```json
|
||||
// packages/blog/turbo.json
|
||||
{ "extends": ["../../../turbo.json"], "tasks": { /* ... */ } }
|
||||
@@ -75,12 +99,13 @@ Why `core-trpc` is `core-composition`:
|
||||
|
||||
Both ESLint and Turborepo enforce the same five-tag rules, but for different reasons:
|
||||
|
||||
| Layer | Runs | Sees | Exempts via |
|
||||
|---|---|---|---|
|
||||
| ESLint `eslint-plugin-boundaries` | lint-time | Direct imports per file | `// @boundaries-ignore` comments |
|
||||
| Turborepo `boundaries` | build-graph time | Entire workspace dependency graph including transitives | None (graph-based, not per-import) |
|
||||
| Layer | Runs | Sees | Exempts via |
|
||||
| --------------------------------- | ---------------- | ------------------------------------------------------- | ---------------------------------- |
|
||||
| ESLint `eslint-plugin-boundaries` | lint-time | Direct imports per file | `// @boundaries-ignore` comments |
|
||||
| Turborepo `boundaries` | build-graph time | Entire workspace dependency graph including transitives | None (graph-based, not per-import) |
|
||||
|
||||
**Why both?**
|
||||
|
||||
- **ESLint** provides fine-grained control (file-level exemptions) and immediate feedback during development
|
||||
- **Turborepo** catches transitive issues (e.g., feature reach through composition packages) and missing declarations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user