Skip to content

ci: phase i.2, architecture guardrails - #75

Merged
axelhamil merged 4 commits into
devfrom
refactor/phase-i-2-guards
Oct 29, 2026
Merged

axelhamil merged 4 commits into
devfrom
refactor/phase-i-2-guards

Conversation

@axelhamil

Copy link
Copy Markdown
Owner
  • pnpm check:boundaries (dependency-cruiser, pre-push and CI): no cross-module import, pure application layer, domain imports only the kernel, packages never import apps, app never imports api; 0 violation on 1046 modules
  • Each rule proven to fail on a temporary probe (cross-module, drizzle in application, hono-pino and @HONO in application, export-from and dynamic import in domain)
  • @packages/test/source-scan: comment and string aware lexer, scans throws, writes and env reads
  • Layering tests: application never throws, routes never write, env read only through shared/env (cron script allowlisted with its reason)
  • Catalog parity: every EventTypes entry needs a reference outside the catalog that is not an onEvent subscription; no emit site uses a type outside the catalog
  • MIGRATIONS_FOLDER moved into the env schema (only violation found); dependency-cruiser gets TypeScript 6 via packageExtensions since TS 7 makes it parse nothing
  • Not added: a cycle rule, Biome noImportCycles already covers the repo

…ncy-cruiser

pnpm check:boundaries runs dependency-cruiser over apps and packages with
six rules: no-cross-module-import, pure-domain-and-application,
domain-imports-only-the-kernel (ddd-kit, events, zod and its own
module), domain-does-not-depend-on-application, packages-never-import-apps
and app-never-imports-api (the front may only import api/client). It runs
in the pre-push hook and as a CI step right after install. Import rules
live here only: the cruiser reads the AST, so re-exports, side-effect
imports, dynamic imports and require are all seen.

Measured with every rule at error: 0 violations (1046 modules, 4463
dependencies cruised), so no baseline file is needed.

dependency-cruiser cannot parse with typescript 7 and silently falls back
to cruising nothing, so a package extension gives it typescript 6 as its
own dependency without touching the workspace compiler. The dist exclusion
is anchored to workspace packages: a bare /dist/ pattern also dropped every
npm package whose entry lives in dist, which made the framework part of
pure-domain-and-application match nothing.
Three scans over apps/api/src, one per rule already written in
apps/api/CLAUDE.md that no import graph can see: domain and application
never throw, routes never write to the database, and the environment is
read only through shared/env. Import rules belong to dependency-cruiser.

First run found one hit: migrate.ts read process.env.MIGRATIONS_FOLDER
directly while already loading shared/env through the logger.
MIGRATIONS_FOLDER now lives in the env schema with the same ./migrations
default. No throw in any application layer and no write in any route
file. The only remaining env reader is cron/sweep.ts, a standalone
script that cannot import shared/env, listed with its reason.
Scans every non-test source file of apps/api/src and packages/*/src,
the catalog package itself excluded, for references to
EventTypes.<NAME>, dotted literals passed to an emit* or addEvent call
and classes declaring eventType = "<type>", then compares them with the
@packages/events catalog both ways.

Any EventTypes reference counts as an emit site, so types routed through
a lookup (cookie consent through ConsentService.append, billing
subscription updates through subscriptionEventType, scim user changes
through the change-kind map of auth.ts) are seen without an allowlist,
and every catalog type has a reference today. Retention, visibility and
description coverage is already enforced by their Record<EventType, ...>
types and the events package tests, so no third test duplicates it.
@axelhamil
axelhamil merged commit 4940b3f into dev Oct 29, 2026
1 check passed
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant