Name the tiers and the direction the arrows point.
Enforcement layer took a set of rules as given and taught how to run them. This lesson is the other half: where the rules come from, and how to keep them the same thing as the picture on the wall and the paragraph in the doc an agent reads before it starts.
The first move is the least technical and the most important: name the tiers. A tier is a folder pattern with a job. Routes, features, components, services. Then the direction: which tier may know about which. Write them top to bottom in the order the arrows point, because that order will matter in step three.
- System
- The two miniature repos from the enforcement lesson, a backend of eleven files and a frontend of twelve, and then this site’s own tree, about 1,080 modules when it was recorded on 12 September 2026.
- Starting point
- Each repo has a diagram drawn once, a paragraph in a doc, and a handwritten checker configuration. They were written by the same person on the same day and already disagree in small ways.
- What must hold
- After every change, the three say the same thing, and an agent reading any one of them reads the architecture that is actually enforced.
Here is the whole declaration model. Read it once; the rest of the lesson fills each field in. A tier has a path and a list of tiers it may import. Some tiers are shared by everyone. Some tiers have doors. A few sentences are easier to say as a deny. And an exception is a dated edge with a reason.
export interface Tier {
/** Declaration order is precedence when paths overlap: the earlier tier wins. */
id: string;
label: string;
/** Regular expression a module path matches to belong to this tier. */
path: string;
/** Tiers this tier may import. Importing itself is always allowed. */
may: string[];
/** Optional: split the tier into isolated groups (a capture group) that may not import each other. */
isolated?: { groups: string; shared?: string[] };
/** Optional: the only paths other tiers may import from this tier. */
doors?: string[];
}
export interface Deny {
name: string;
comment: string;
/** A tier id, or a regular expression when it starts with `^`. */
from: string;
/** Regular expression over the imported path or bare specifier. */
to: string;
fromNot?: string;
}
export interface Exception {
rule: string;
from: string;
to: string;
reason: string;
/** ISO date after which the exception should be revisited. */
until: string;
}
export interface Architecture {
name: string;
tiers: Tier[];
/** Tier ids every tier may import without saying so. */
shared?: string[];
/** Cross-cutting sentences that are easier to say as a deny. */
deny?: Deny[];
exceptions?: Exception[];
} A list of tiers in arrow order, each with a path pattern and a one-line job. If two tiers have the same job, they are one tier. If one tier has two jobs, it is two.
Say what each tier may import. Everything else is forbidden.
The enforcement lesson’s handwritten rules were deny-lists: “services never import server”. A deny-list is easy to start and impossible to finish, because it only forbids what you thought of. An allow-list says what a tier may import, and the derivation turns that into one forbidden rule per tier: anything inside the architecture that is not on the list.
This is the backend declared. Six tiers. Kernel is shared, so every tier may import it without saying so. One sentence, memory adapters, stays a deny because it cuts across tiers by file name rather than by folder.
import type { Architecture } from './architecture';
// The enforcement lesson's miniature backend, declared once. Compare with the
// four handwritten rules it replaces: same verdicts, one source.
export const backend: Architecture = {
name: 'Miniature backend-for-frontend',
tiers: [
{ id: 'routes', label: 'Routes', path: '^routes/', may: ['server', 'services'] },
{
id: 'server',
label: 'Server',
path: '^server/',
may: ['root', 'services'],
doors: ['^server/root\\.ts$']
},
{ id: 'root', label: 'Root', path: '^root/', may: ['services'] },
{ id: 'services', label: 'Services', path: '^services/', may: ['http'] },
{ id: 'http', label: 'Http', path: '^http/', may: [] },
{ id: 'kernel', label: 'Kernel', path: '^kernel/', may: [] }
],
shared: ['kernel'],
deny: [
{
name: 'memory-adapters-are-for-root-and-tests',
comment: 'Memory mode is chosen at the root or in a test, never by a handler or a service.',
from: '^(?!root/)',
fromNot: '\\.spec\\.ts$',
to: '\\.memory\\.ts$'
}
]
};
And the derivation, in one function. Each tier becomes a rule whose to matches anything inside the architecture and whose pathNot is the tier itself, its
permissions, and the shared tiers. Doors and isolated groups add a rule each. Denies pass through.
/** The forbidden rules a checker runs, derived from the tiers. */
export function toRules(architecture: Architecture): Rule[] {
const { tiers, shared = [], deny = [] } = architecture;
const byId = new Map(tiers.map((tier) => [tier.id, tier]));
const tierPath = (id: string) => {
const tier = byId.get(id);
if (!tier) throw new Error(`Unknown tier "${id}" in ${architecture.name}`);
return tier.path;
};
const inside = group(tiers.map((tier) => tier.path));
const rules: Rule[] = [];
for (const [index, tier] of tiers.entries()) {
// Declaration order is precedence: a file that matches two tiers belongs to the earlier one,
// so declare the specific tier before the general one it sits inside.
const earlier = tiers.slice(0, index).map((entry) => entry.path);
const allowed = [tier.path, ...tier.may.map(tierPath), ...shared.map(tierPath)];
const named = [...tier.may, ...shared.filter((id) => id !== tier.id)];
rules.push({
name: `${tier.id}-imports-only-${named.length ? named.join('-') : 'itself'}`,
severity: 'error',
comment: named.length
? `${tier.label} may import ${named.map((id) => byId.get(id)?.label ?? id).join(', ')} and nothing else.`
: `${tier.label} imports nothing outside itself.`,
from: { path: tier.path, pathNot: earlier.length ? group(earlier) : undefined },
to: { path: inside, pathNot: group(allowed) }
});
if (tier.isolated) {
const { groups, shared: sharedGroups = [] } = tier.isolated;
rules.push({
name: `${tier.id}-groups-stay-apart`,
severity: 'error',
comment: `One ${tier.label.toLowerCase()} per folder. What two share is passed in, or lives in a named shared one.`,
from: { path: groups },
to: {
path: tier.path,
pathNot: groups.replace(/\([^)]*\)/, group(['$1', ...sharedGroups]))
}
});
}
if (tier.doors?.length) {
rules.push({
name: `${tier.id}-through-its-doors`,
severity: 'error',
comment: `Other tiers reach ${tier.label} only through ${tier.doors.join(', ')}.`,
from: { pathNot: tier.path },
to: { path: tier.path, pathNot: group(tier.doors) }
});
}
}
for (const entry of deny) {
rules.push({
name: entry.name,
severity: 'error',
comment: entry.comment,
from: {
path: entry.from.startsWith('^') ? entry.from : tierPath(entry.from),
pathNot: entry.fromNot
},
to: { path: entry.to }
});
}
return rules;
} The test that matters: on every agent change from the enforcement lesson, the derived rules reach the same verdict as the handwritten ones. Where the deny-list said nothing, the allow-list is stricter. A route importing the transport port broke no handwritten rule; it breaks the derived one, because nobody said routes may see http.
Every tier’s permissions written down, and a run on the tree as it is. Expect surprises: an allow-list is a census of the permissions you had granted without noticing.
Name the entry points, not the files.
A permission says who may look at a tier. It does not say where. “Routes may import server” allowed the handler in the enforcement lesson to import a Supabase adapter, because the adapter lives inside server. The handwritten fix forbade the adapter’s folder by name. That rule is defeated by the next barrel file that re-exports it.
Doors say it the other way round: the only paths other tiers may reach in this tier. The server tier’s door is its root. Add an adapter, add a barrel, move a file; the door is still the door. Two more fields do similar work. An isolated tier is split into groups that may not import each other, which is how one feature stays one feature. And shared names the tiers everyone may import, so the permission is stated once instead of on every row.
Declaration order matters here. A file that matches two tier patterns belongs to the earlier one, so declare the specific tier before the general one it sits inside. The animation studio on this site lives in the design-system folder; it is declared first, so its files’ own imports are judged as studio, not design.
Order decides only which tier a file’s imports are judged under. It does not narrow who may import the file: the design tier’s pattern still covers the studio’s folder, so any tier allowed to import design can reach the studio’s files too. To keep a nested tier private, exclude its folder from the general tier’s pattern as well.
import type { Architecture } from './architecture';
// The enforcement lesson's miniature frontend, declared once.
export const frontend: Architecture = {
name: 'Miniature frontend',
tiers: [
{ id: 'routes', label: 'Routes', path: '^routes/', may: ['features', 'components', 'app'] },
{
id: 'features',
label: 'Features',
path: '^features/',
may: ['components', 'display', 'services'],
isolated: { groups: '^features/([^/]+)/' }
},
{ id: 'components', label: 'Components', path: '^components/chrome/', may: ['display'] },
{ id: 'display', label: 'Display', path: '^components/display/', may: [] },
{ id: 'app', label: 'App', path: '^app/', may: ['services', 'http'] },
{ id: 'services', label: 'Services', path: '^services/', may: ['http'] },
{ id: 'http', label: 'Http', path: '^http/', may: [] }
],
deny: [
{
name: 'display-primitives-stay-portable',
comment: 'A primitive is props in, markup out. No app state, no navigation, no content.',
from: 'display',
to: '^\\$app/'
}
]
};
Doors on any tier that has internals, isolation on any tier that holds many small things, and a shared list short enough to read aloud.
Generate the diagram, the doc, and the check. Never edit them.
Three more functions read the same declaration. One draws the diagram: a node per tier in declaration order, an arrow per permission. One prints the doc table an agent will read. One writes the real tool’s configuration, with every path prefixed for wherever the cruise starts, and turns the dated exceptions into the tool’s known-violations baseline.
The three derivationsDiagram, doc, and checker
/** The diagram: one node per tier, one arrow per permission, top to bottom in declaration order. */
export function toDiagram(architecture: Architecture) {
const { tiers, shared = [] } = architecture;
const nodes = tiers.map((tier, index) => ({
id: tier.id,
label: tier.label,
kind: shared.includes(tier.id)
? 'shared'
: tier.doors
? `doors: ${tier.doors.length}`
: undefined,
x: 130 + (index % 2) * 240,
y: 56 + Math.floor(index / 2) * 120
}));
const edges = tiers.flatMap((tier) =>
[...tier.may, ...shared.filter((id) => id !== tier.id && !tier.may.includes(id))].map((to) => ({
id: `${tier.id}→${to}`,
from: tier.id,
to,
label: shared.includes(to) && !tier.may.includes(to) ? 'shared' : undefined
}))
);
return { nodes, edges };
}
/** The doc table, as Markdown, so the paragraph is generated too. */
export function toDoc(architecture: Architecture): string {
const { tiers, shared = [], deny = [], exceptions = [] } = architecture;
const lines = [
`# ${architecture.name}`,
'',
'| Tier | May import | Doors |',
'| --- | --- | --- |',
...tiers.map(
(tier) =>
`| ${tier.label} | ${[...tier.may, ...shared.filter((id) => id !== tier.id)].join(', ') || 'nothing'} | ${tier.doors?.join(', ') ?? 'any file'} |`
)
];
if (deny.length) lines.push('', ...deny.map((entry) => `- **${entry.name}**: ${entry.comment}`));
if (exceptions.length)
lines.push(
'',
'Tolerated until the date shown:',
...exceptions.map((e) => `- ${e.from} → ${e.to} (${e.rule}, until ${e.until}): ${e.reason}`)
);
return lines.join('\n') + '\n';
} /** The real tool's configuration, with every path prefixed for where the cruise starts. */
export function toCruiserConfig(architecture: Architecture, prefix = '') {
const fix = (pattern?: string) =>
pattern === undefined ? undefined : pattern.replaceAll('^', `^${prefix}`);
return {
forbidden: toRules(architecture).map((rule) => ({
name: rule.name,
severity: rule.severity,
comment: rule.comment,
from: { path: fix(rule.from.path), pathNot: fix(rule.from.pathNot) },
to: { path: fix(rule.to.path), pathNot: fix(rule.to.pathNot) }
}))
};
}
/** The tool's known-violations baseline, derived from the dated exceptions. */
export function toKnownViolations(architecture: Architecture, prefix = '') {
return (architecture.exceptions ?? []).map((exception) => ({
type: 'dependency',
from: `${prefix}${exception.from}`,
to: `${prefix}${exception.to}`,
rule: { severity: 'error', name: exception.rule }
}));
}
/** Exceptions past their date are findings again. */
export function expiredExceptions(architecture: Architecture, today: string): Exception[] {
return (architecture.exceptions ?? []).filter((exception) => exception.until < today);
} A script writes the generated files next to the declaration, and a test compares each committed file with a fresh derivation. Edit a declaration without regenerating and the test fails. Edit a generated file by hand and the test fails. That is the whole discipline: exactly one thing is a source.
// GENERATED by generate.ts from the declaration next to it. Do not edit.
module.exports = {
"forbidden": [
{
"name": "routes-imports-only-server-services-kernel",
"severity": "error",
"comment": "Routes may import Server, Services, Kernel and nothing else.",
"from": {
"path": "^repo/routes/"
},
"to": {
"path": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/|^repo/kernel/)",
"pathNot": "(^repo/routes/|^repo/server/|^repo/services/|^repo/kernel/)"
}
},
{
"name": "server-imports-only-root-services-kernel",
"severity": "error",
"comment": "Server may import Root, Services, Kernel and nothing else.",
"from": {
"path": "^repo/server/",
"pathNot": "(^repo/routes/)"
},
"to": {
"path": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/|^repo/kernel/)",
"pathNot": "(^repo/server/|^repo/root/|^repo/services/|^repo/kernel/)"
}
},
{
"name": "server-through-its-doors",
"severity": "error",
"comment": "Other tiers reach Server only through ^server/root\\.ts$.",
"from": {
"pathNot": "^repo/server/"
},
"to": {
"path": "^repo/server/",
"pathNot": "(^repo/server/root\\.ts$)"
}
},
{
"name": "root-imports-only-services-kernel",
"severity": "error",
"comment": "Root may import Services, Kernel and nothing else.",
"from": {
"path": "^repo/root/",
"pathNot": "(^repo/routes/|^repo/server/)"
},
"to": {
"path": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/|^repo/kernel/)",
"pathNot": "(^repo/root/|^repo/services/|^repo/kernel/)"
}
},
{
"name": "services-imports-only-http-kernel",
"severity": "error",
"comment": "Services may import Http, Kernel and nothing else.",
"from": {
"path": "^repo/services/",
"pathNot": "(^repo/routes/|^repo/server/|^repo/root/)"
},
"to": {
"path": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/|^repo/kernel/)",
"pathNot": "(^repo/services/|^repo/http/|^repo/kernel/)"
}
},
{
"name": "http-imports-only-kernel",
"severity": "error",
"comment": "Http may import Kernel and nothing else.",
"from": {
"path": "^repo/http/",
"pathNot": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/)"
},
"to": {
"path": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/|^repo/kernel/)",
"pathNot": "(^repo/http/|^repo/kernel/)"
}
},
{
"name": "kernel-imports-only-itself",
"severity": "error",
"comment": "Kernel imports nothing outside itself.",
"from": {
"path": "^repo/kernel/",
"pathNot": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/)"
},
"to": {
"path": "(^repo/routes/|^repo/server/|^repo/root/|^repo/services/|^repo/http/|^repo/kernel/)",
"pathNot": "(^repo/kernel/|^repo/kernel/)"
}
},
{
"name": "memory-adapters-are-for-root-and-tests",
"severity": "error",
"comment": "Memory mode is chosen at the root or in a test, never by a handler or a service.",
"from": {
"path": "^repo/(?!root/)",
"pathNot": "\\.spec\\.ts$"
},
"to": {
"path": "\\.memory\\.ts$"
}
}
],
"options": {
"tsPreCompilationDeps": true,
"exclude": {
"path": "node_modules|\\.(spec|test)\\.ts$"
},
"doNotFollow": {
"path": "node_modules"
}
}
};
# Miniature backend-for-frontend
| Tier | May import | Doors |
| --- | --- | --- |
| Routes | server, services, kernel | any file |
| Server | root, services, kernel | ^server/root\.ts$ |
| Root | services, kernel | any file |
| Services | http, kernel | any file |
| Http | kernel | any file |
| Kernel | nothing | any file |
- **memory-adapters-are-for-root-and-tests**: Memory mode is chosen at the root or in a test, never by a handler or a service.
The generated configuration, run by the real tool against the same agent change the enforcement lesson caught. The rule has a different name and the same verdict, on both surfaces.
$ npx dependency-cruiser --config generated/backend.dependency-cruiser.cjs --output-type err-long repo
error server-through-its-doors: repo/routes/api/notes/+server.ts → repo/server/supabase/notes.ts
Other tiers reach Server only through ^server/root\.ts$.
error server-through-its-doors: repo/routes/api/notes/+server.ts → repo/server/supabase/client.ts
Other tiers reach Server only through ^server/root\.ts$.
x 2 dependency violations (2 errors, 0 warnings). 10 modules, 17 dependencies cruised.
$ echo $?
2
$ npx dependency-cruiser --config generated/frontend.dependency-cruiser.cjs --output-type err-long 'frontend-repo/**/*.svelte' 'frontend-repo/**/*.ts'
error components-imports-only-display: frontend-repo/components/chrome/Header.svelte → frontend-repo/features/session/session.svelte.ts
Components may import Display and nothing else.
x 1 dependency violations (1 errors, 0 warnings). 14 modules, 26 dependencies cruised.
$ echo $?
1
Now change the declaration yourself. The editor holds both miniature repos. Untick a permission and the rules, the diagram, the doc, and the verdict all move at once, because none of them is stored; each is a function of the declaration in front of you.
Change one permission. Watch the rules, the picture, and the verdict move together.
The declaration below is the one the lesson shows. Tick or untick a cell to change what a tier may import. Everything on the right is derived from it, live, against the miniature repo and the agent’s change you pick.
| Tier | Routes | Server | Root | Services | Http | Kernel |
|---|---|---|---|---|---|---|
| Routes | ||||||
| Server | ||||||
| Root | ||||||
| Services | ||||||
| Http | ||||||
| Kernel |
0 dependency violations (0 errors, 0 warnings). 11 modules, 19 dependencies cruised.
The diagram, derived
One node per tier, one arrow per permission, in declaration order. A shared tier is reachable from every node.
View connections as text
- Routes
- Server
- Root
- Services
- Http
- Kernel
- Routes points to Server
- Routes points to Services
- Routes shared Kernel
- Server points to Root
- Server points to Services
- Server shared Kernel
- Root points to Services
- Root shared Kernel
- Services points to Http
- Services shared Kernel
- Http shared Kernel
routes-imports-only-server-services-kernelRoutes may import Server, Services, Kernel and nothing else.
from^routes/→(^routes/|^server/|^root/|^services/|^http/|^kernel/)not(^routes/|^server/|^services/|^kernel/)server-imports-only-root-services-kernelServer may import Root, Services, Kernel and nothing else.
from^server/not(^routes/)→(^routes/|^server/|^root/|^services/|^http/|^kernel/)not(^server/|^root/|^services/|^kernel/)server-through-its-doorsOther tiers reach Server only through ^server/root\.ts$.
from*not^server/→^server/not(^server/root\.ts$)root-imports-only-services-kernelRoot may import Services, Kernel and nothing else.
from^root/not(^routes/|^server/)→(^routes/|^server/|^root/|^services/|^http/|^kernel/)not(^root/|^services/|^kernel/)services-imports-only-http-kernelServices may import Http, Kernel and nothing else.
from^services/not(^routes/|^server/|^root/)→(^routes/|^server/|^root/|^services/|^http/|^kernel/)not(^services/|^http/|^kernel/)http-imports-only-kernelHttp may import Kernel and nothing else.
from^http/not(^routes/|^server/|^root/|^services/)→(^routes/|^server/|^root/|^services/|^http/|^kernel/)not(^http/|^kernel/)kernel-imports-only-itselfKernel imports nothing outside itself.
from^kernel/not(^routes/|^server/|^root/|^services/|^http/)→(^routes/|^server/|^root/|^services/|^http/|^kernel/)not(^kernel/|^kernel/)memory-adapters-are-for-root-and-testsMemory mode is chosen at the root or in a test, never by a handler or a service.
from^(?!root/)not\.spec\.ts$→\.memory\.ts$
The engine, repos, and agent changes are the enforcement lesson’s. Only the declaration is new: it is the single source, and every panel on the right is a function of it.
A generator, its output committed, and a test that fails when either side is stale. The diagram in the doc is now the diagram the checker enforces.
Run it on the real tree, and fix the declaration, not the tree.
This site, declared the same way: thirteen tiers at first, two shared, two denies. The first run is the honest part. Eighty-two edges. Not eighty-two problems: eighty-two permissions the tree had granted itself that nobody had written down, and a handful of real findings hiding among them.
$ npx dependency-cruiser --config src/lib/content/lessons/architecture-as-rules/examples/generated/site.dependency-cruiser.cjs 'src/**/*.svelte' 'src/**/*.ts' # first declaration
error server-imports-only-services-root-http-kernel-config: src/lib/server/root.ts → src/lib/app/return-to.ts
Server may import Services, Root, Http, Kernel, Config and nothing else.
error server-imports-only-services-root-http-kernel-config: src/lib/server/highlight.ts → src/lib/components/content/code.ts
Server may import Services, Root, Http, Kernel, Config and nothing else.
error pages-imports-only-features-components-content-app-kernel-config: src/routes/foundations/animations/+page.svelte → src/lib/design-system/animations/AnimationGallery.svelte
Pages may import Features, Components, Content, Browser app, Kernel, Config
and nothing else.
error pages-imports-only-features-components-content-app-kernel-config: src/routes/foundations/+page.svelte → src/lib/design-system/visualizations/Visualizations.svelte
Pages may import Features, Components, Content, Browser app, Kernel, Config
and nothing else.
error pages-imports-only-features-components-content-app-kernel-config: src/routes/auth/sign-in/+page.svelte → src/lib/services/session/index.ts
Pages may import Features, Components, Content, Browser app, Kernel, Config
and nothing else.
error pages-imports-only-features-components-content-app-kernel-config: src/routes/animation-studio/+page.svelte → src/lib/design-system/animation-studio/AnimationStudio.svelte
Pages may import Features, Components, Content, Browser app, Kernel, Config
and nothing else.
error pages-imports-only-features-components-content-app-kernel-config: src/routes/account/+page.svelte → src/lib/services/session/index.ts
Pages may import Features, Components, Content, Browser app, Kernel, Config
and nothing else.
error pages-imports-only-features-components-content-app-kernel-config: src/routes/+layout.svelte → src/lib/styles/app.css
Pages may import Features, Components, Content, Browser app, Kernel, Config
and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/visitor/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/template-method/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/strategy/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/state-machine/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/singleton/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/registry/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/pub-sub/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/proxy/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/prototype/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/observer/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/object-pool/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/null-object/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/multiton/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/memento/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/mediator/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/lazy-initialization/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/iterator/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/interpreter/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/flyweight/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/facade/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/decorator/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/composite/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/command/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/chain-of-responsibility/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/builder/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/bridge/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/adapter/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/patterns/abstract-factory/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/foundations/+page.server.ts → src/lib/design-system/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/dsa/stack/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/dsa/linked-list/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/dsa/dynamic-array/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/dsa/binary-heap/+page.server.ts → src/lib/components/content/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/auth/sign-out/+server.ts → src/lib/app/return-to.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/auth/sign-in/+page.server.ts → src/lib/app/return-to.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-kernel-config: src/routes/auth/callback/+server.ts → src/lib/app/return-to.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Kernel, Config and nothing else.
error features-imports-only-components-content-design-services-app-kernel-config: src/lib/features/engagement/tracker.ts → src/lib/http/index.ts
Features may import Components, Content, Design system & styles, Services,
Browser app, Kernel, Config and nothing else.
error features-groups-stay-apart: src/lib/features/architecture-as-rules/types.ts → src/lib/features/enforcement-layer/types.ts
One features per folder. What two share is passed in, or lives in a named
shared one.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/Visualizations.svelte → src/lib/components/visualization/VisualizationFrame.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/Visualizations.svelte → src/lib/components/visualization/KnowledgeGraph.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/TraversalDemo.svelte → src/lib/components/visualization/VisualizationFrame.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/TraversalDemo.svelte → src/lib/components/visualization/TreeDiagram.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/TraversalDemo.svelte → src/lib/components/visualization/math.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/TraversalDemo.svelte → src/lib/components/navigation/SegmentedControl.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/TraversalDemo.svelte → src/lib/components/display/Button.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/MemoryDemo.svelte → src/lib/components/visualization/VisualizationFrame.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/MemoryDemo.svelte → src/lib/components/visualization/MemoryLayout.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/MemoryDemo.svelte → src/lib/components/navigation/SegmentedControl.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/GrowthDemo.svelte → src/lib/components/visualization/VisualizationFrame.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/GrowthDemo.svelte → src/lib/components/visualization/LineChart.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/GrowthDemo.svelte → src/lib/components/navigation/SegmentedControl.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/data.ts → src/lib/components/visualization/types.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/BenchmarkDemo.svelte → src/lib/components/visualization/VisualizationFrame.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/BenchmarkDemo.svelte → src/lib/components/visualization/BarChart.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/visualizations/BenchmarkDemo.svelte → src/lib/components/navigation/SegmentedControl.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/StudyScene.svelte → src/lib/features/stack/HistoryBoard.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/StudyScene.svelte → src/lib/features/observer/ObserverScene.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/studies.ts → src/lib/features/stack/history-film.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/studies.ts → src/lib/features/observer/observer-story.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/studies.ts → src/lib/components/animation/types.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/AnimationGallery.svelte → src/lib/components/layout/Shell.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animations/AnimationGallery.svelte → src/lib/components/animation/AnimationPlayer.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/studio.ts → src/lib/components/animation/types.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/observer-playground.ts → src/lib/content/lessons/observer/examples/cart.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/observer-playground.ts → src/lib/components/animation/types.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/identity-study.ts → src/lib/components/animation/types.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/AnimationStudio.svelte → src/lib/components/layout/Shell.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/AnimationStudio.svelte → src/lib/components/animation/AnimationPlayer.svelte
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/algorithm-studies.ts → src/lib/content/lessons/levenshtein-distance/examples/locations.ts
Design system & styles may import Kernel, Config and nothing else.
error design-imports-only-kernel-config: src/lib/design-system/animation-studio/algorithm-studies.ts → src/lib/components/animation/types.ts
Design system & styles may import Kernel, Config and nothing else.
error components-imports-only-design-kernel-config: src/lib/components/chrome/ThemeToggle.svelte → src/lib/features/preferences/preferences.svelte.ts
Components may import Design system & styles, Kernel, Config and nothing
else.
error components-imports-only-design-kernel-config: src/lib/components/chrome/AccountControl.svelte → src/lib/features/session/session.svelte.ts
Components may import Design system & styles, Kernel, Config and nothing
else.
error components-imports-only-design-kernel-config: src/lib/components/catalog/TopicCard.svelte → src/lib/content/catalog/links.ts
Components may import Design system & styles, Kernel, Config and nothing
else.
error components-imports-only-design-kernel-config: src/lib/components/catalog/CatalogPage.svelte → src/lib/features/catalog/CatalogPreview.svelte
Components may import Design system & styles, Kernel, Config and nothing
else.
x 82 dependency violations (82 errors, 0 warnings). 1083 modules, 3121 dependencies cruised.
$ echo $?
82
Read a census by rule, not by edge. Thirty-six under route handlers: they import component types for highlighting and a browser helper, so handlers may see components and app. Thirty-two under the design system, and two corrections behind them. Seventeen were the design system’s visualizations importing components, so design may now import components. The other fifteen came from the animation studies, which live in that folder but showcase components and lesson scenes, so the studio became a fourteenth tier, declared before design. One under feature isolation was a file written for this lesson an hour earlier, reaching into the enforcement lesson’s feature folder. The check found it before a reviewer did.
Every fix went into the declaration. Thirteen edges after the first correction, six after the second. The six that remain are the questions, and they are nearly the same six the handwritten deny-list found: two pages that call a service directly, three chrome and catalog components that read a feature’s state, and one the deny-list never mentioned, the server root importing a browser helper. The deny-list’s sixth, a feature importing the catalog’s filter, is gone because the declaration says catalog is shared. That is a decision, written where it can be read.
$ npx dependency-cruiser --config src/lib/content/lessons/architecture-as-rules/examples/generated/site.dependency-cruiser.cjs 'src/**/*.svelte' 'src/**/*.ts' # second declaration: tiers corrected once
error server-imports-only-services-root-http-components-kernel-config: src/lib/server/root.ts → src/lib/app/return-to.ts
Server may import Services, Root, Http, Components, Kernel, Config and
nothing else.
error pages-imports-only-features-components-content-design-studio-app-kernel-config: src/routes/auth/sign-in/+page.svelte → src/lib/services/session/index.ts
Pages may import Features, Components, Content, Design system & styles,
Animation studio, Browser app, Kernel, Config and nothing else.
error pages-imports-only-features-components-content-design-studio-app-kernel-config: src/routes/account/+page.svelte → src/lib/services/session/index.ts
Pages may import Features, Components, Content, Design system & styles,
Animation studio, Browser app, Kernel, Config and nothing else.
error handlers-imports-only-server-services-content-root-http-features-components-app-kernel-config: src/routes/foundations/+page.server.ts → src/lib/design-system/examples.ts
Route handlers may import Server, Services, Content, Root, Http, Features,
Components, Browser app, Kernel, Config and nothing else.
error design-imports-only-components-kernel-config: src/lib/design-system/animations/StudyScene.svelte → src/lib/features/stack/HistoryBoard.svelte
Design system & styles may import Components, Kernel, Config and nothing
else.
error design-imports-only-components-kernel-config: src/lib/design-system/animations/StudyScene.svelte → src/lib/features/observer/ObserverScene.svelte
Design system & styles may import Components, Kernel, Config and nothing
else.
error design-imports-only-components-kernel-config: src/lib/design-system/animations/studies.ts → src/lib/features/stack/history-film.ts
Design system & styles may import Components, Kernel, Config and nothing
else.
error design-imports-only-components-kernel-config: src/lib/design-system/animations/studies.ts → src/lib/features/observer/observer-story.ts
Design system & styles may import Components, Kernel, Config and nothing
else.
error design-imports-only-components-kernel-config: src/lib/design-system/animation-studio/observer-playground.ts → src/lib/content/lessons/observer/examples/cart.ts
Design system & styles may import Components, Kernel, Config and nothing
else.
error design-imports-only-components-kernel-config: src/lib/design-system/animation-studio/algorithm-studies.ts → src/lib/content/lessons/levenshtein-distance/examples/locations.ts
Design system & styles may import Components, Kernel, Config and nothing
else.
error components-imports-only-design-content-kernel-config: src/lib/components/chrome/ThemeToggle.svelte → src/lib/features/preferences/preferences.svelte.ts
Components may import Design system & styles, Content, Kernel, Config and
nothing else.
error components-imports-only-design-content-kernel-config: src/lib/components/chrome/AccountControl.svelte → src/lib/features/session/session.svelte.ts
Components may import Design system & styles, Content, Kernel, Config and
nothing else.
error components-imports-only-design-content-kernel-config: src/lib/components/catalog/CatalogPage.svelte → src/lib/features/catalog/CatalogPreview.svelte
Components may import Design system & styles, Content, Kernel, Config and
nothing else.
x 13 dependency violations (13 errors, 0 warnings). 1085 modules, 3129 dependencies cruised.
$ echo $?
13
$ npx dependency-cruiser --config src/lib/content/lessons/architecture-as-rules/examples/generated/site.dependency-cruiser.cjs 'src/**/*.svelte' 'src/**/*.ts' # third declaration
error server-imports-only-services-root-http-components-kernel-config: src/lib/server/root.ts → src/lib/app/return-to.ts
Server may import Services, Root, Http, Components, Kernel, Config and
nothing else.
error pages-imports-only-features-components-content-design-studio-app-kernel-config: src/routes/auth/sign-in/+page.svelte → src/lib/services/session/index.ts
Pages may import Features, Components, Content, Design system & styles,
Animation studio, Browser app, Kernel, Config and nothing else.
error pages-imports-only-features-components-content-design-studio-app-kernel-config: src/routes/account/+page.svelte → src/lib/services/session/index.ts
Pages may import Features, Components, Content, Design system & styles,
Animation studio, Browser app, Kernel, Config and nothing else.
error components-imports-only-design-content-kernel-config: src/lib/components/chrome/ThemeToggle.svelte → src/lib/features/preferences/preferences.svelte.ts
Components may import Design system & styles, Content, Kernel, Config and
nothing else.
error components-imports-only-design-content-kernel-config: src/lib/components/chrome/AccountControl.svelte → src/lib/features/session/session.svelte.ts
Components may import Design system & styles, Content, Kernel, Config and
nothing else.
error components-imports-only-design-content-kernel-config: src/lib/components/catalog/CatalogPage.svelte → src/lib/features/catalog/CatalogPreview.svelte
Components may import Design system & styles, Content, Kernel, Config and
nothing else.
x 6 dependency violations (6 errors, 0 warnings). 1086 modules, 3129 dependencies cruised.
$ echo $?
6
The six become exceptions in the declaration: the edge, the rule, a reason, and a date. The generator turns them into the tool’s baseline, and the run that ignores the baseline is green with six known edges named. A test fails on the day an exception expires, so a tolerated edge becomes a finding again on schedule instead of forever.
This site’s declarationFourteen tiers, six dated exceptions
import type { Architecture } from './architecture';
// This site's frontend and platform tiers, declared once. The first version
// of any declaration is wrong somewhere; the baseline run says where, and
// the fix goes here, never into the generated files.
export const site: Architecture = {
name: 'heyrian.dev',
tiers: [
{
id: 'pages',
label: 'Pages',
path: '^src/routes/.*\\+(page|layout)\\.svelte$',
may: ['features', 'components', 'content', 'design', 'studio', 'app']
},
{
id: 'handlers',
label: 'Route handlers',
path: '^src/routes/.*(\\+(page|layout)(\\.server)?\\.ts|\\+server\\.ts)$|^src/hooks\\.server\\.ts$',
may: [
'server',
'services',
'content',
'root',
'http',
'features',
'components',
'app',
'design'
]
},
{
id: 'features',
label: 'Features',
path: '^src/lib/features/',
may: ['components', 'content', 'design', 'services', 'app', 'http'],
isolated: {
groups: '^src/lib/features/([^/]+)/',
shared: ['reader-context', 'preferences', 'catalog']
}
},
{
id: 'content',
label: 'Content',
path: '^src/lib/content/',
may: ['features', 'components', 'design']
},
{
id: 'studio',
label: 'Animation studio',
path: '^src/lib/design-system/(animation-studio|animations)/',
may: ['components', 'content', 'design', 'features']
},
{
id: 'components',
label: 'Components',
path: '^src/lib/components/',
may: ['design', 'content']
},
{
id: 'design',
label: 'Design system & styles',
path: '^src/lib/(design-system|styles)/',
may: ['components']
},
{ id: 'app', label: 'Browser app', path: '^src/lib/app/', may: ['services', 'root', 'http'] },
{
id: 'server',
label: 'Server',
path: '^src/lib/server/',
may: ['services', 'root', 'http', 'components'],
doors: ['^src/lib/server/(root|respond|highlight|regions)\\.ts$']
},
{ id: 'root', label: 'Root', path: '^src/lib/root/', may: ['services', 'http'] },
{ id: 'services', label: 'Services', path: '^src/lib/services/', may: ['http'] },
{ id: 'http', label: 'Http', path: '^src/lib/http/', may: [] },
{ id: 'kernel', label: 'Kernel', path: '^src/lib/kernel/', may: [] },
{ id: 'config', label: 'Config', path: '^src/lib/config/', may: ['content'] }
],
shared: ['kernel', 'config'],
deny: [
{
name: 'display-primitives-stay-portable',
comment: 'A primitive is props in, markup out. No app state, no navigation, no content.',
from: '^src/lib/components/display/',
to: '^src/lib/(features|content|server|services|app)/|^\\$app/'
},
{
name: 'browser-never-imports-supabase',
comment:
'The browser never holds a key. Only server code may import the Supabase client or adapters.',
from: '^(?!src/lib/server/)',
fromNot: '\\+server\\.ts$|\\+(page|layout)\\.server\\.ts$|^src/hooks\\.server',
to: 'node_modules/@supabase/|^src/lib/server/supabase/'
}
],
exceptions: [
{
rule: 'pages-imports-only-features-components-content-design-studio-app-kernel-config',
from: 'src/routes/auth/sign-in/+page.svelte',
to: 'src/lib/services/session/index.ts',
reason:
'The page calls the session service’s browser half directly. Decide whether pages may, or route it through the browser root.',
until: '2026-10-15'
},
{
rule: 'pages-imports-only-features-components-content-design-studio-app-kernel-config',
from: 'src/routes/account/+page.svelte',
to: 'src/lib/services/session/index.ts',
reason: 'Same question as sign-in.',
until: '2026-10-15'
},
{
rule: 'components-imports-only-design-content-kernel-config',
from: 'src/lib/components/chrome/ThemeToggle.svelte',
to: 'src/lib/features/preferences/preferences.svelte.ts',
reason:
'Chrome reads the preferences feature. Either preferences become shared state chrome may read, or the layout passes the theme down.',
until: '2026-10-15'
},
{
rule: 'components-imports-only-design-content-kernel-config',
from: 'src/lib/components/chrome/AccountControl.svelte',
to: 'src/lib/features/session/session.svelte.ts',
reason: 'Chrome reads the session feature. Same decision as the theme toggle.',
until: '2026-10-15'
},
{
rule: 'components-imports-only-design-content-kernel-config',
from: 'src/lib/components/catalog/CatalogPage.svelte',
to: 'src/lib/features/catalog/CatalogPreview.svelte',
reason:
'A shared page component renders a feature’s preview. Either the preview moves below the line or catalog is declared a component.',
until: '2026-10-15'
},
{
rule: 'server-imports-only-services-root-http-components-kernel-config',
from: 'src/lib/server/root.ts',
to: 'src/lib/app/return-to.ts',
reason:
'A return-to helper is shared by the server and the browser app. It belongs in kernel or http.',
until: '2026-10-15'
}
]
};
[
{
"type": "dependency",
"from": "src/routes/auth/sign-in/+page.svelte",
"to": "src/lib/services/session/index.ts",
"rule": {
"severity": "error",
"name": "pages-imports-only-features-components-content-design-studio-app-kernel-config"
}
},
{
"type": "dependency",
"from": "src/routes/account/+page.svelte",
"to": "src/lib/services/session/index.ts",
"rule": {
"severity": "error",
"name": "pages-imports-only-features-components-content-design-studio-app-kernel-config"
}
},
{
"type": "dependency",
"from": "src/lib/components/chrome/ThemeToggle.svelte",
"to": "src/lib/features/preferences/preferences.svelte.ts",
"rule": {
"severity": "error",
"name": "components-imports-only-design-content-kernel-config"
}
},
{
"type": "dependency",
"from": "src/lib/components/chrome/AccountControl.svelte",
"to": "src/lib/features/session/session.svelte.ts",
"rule": {
"severity": "error",
"name": "components-imports-only-design-content-kernel-config"
}
},
{
"type": "dependency",
"from": "src/lib/components/catalog/CatalogPage.svelte",
"to": "src/lib/features/catalog/CatalogPreview.svelte",
"rule": {
"severity": "error",
"name": "components-imports-only-design-content-kernel-config"
}
},
{
"type": "dependency",
"from": "src/lib/server/root.ts",
"to": "src/lib/app/return-to.ts",
"rule": {
"severity": "error",
"name": "server-imports-only-services-root-http-components-kernel-config"
}
}
]
$ node --experimental-strip-types generate.ts # writes site.known-violations.json from the dated exceptions
$ npx dependency-cruiser --config src/lib/content/lessons/architecture-as-rules/examples/generated/site.dependency-cruiser.cjs 'src/**/*.svelte' 'src/**/*.ts' --ignore-known src/lib/content/lessons/architecture-as-rules/examples/generated/site.known-violations.json
✔ no dependency violations found (1086 modules, 3129 dependencies cruised)
‼ 6 known violations ignored. Run with --no-ignore-known to see them.
$ echo $?
0
Know what that declaration is on this site today. It is this lesson’s artifact, recorded on
12 September 2026, and it is not the site’s gate. The site’s CI runs yarn test:architecture and yarn check:architecture: a handwritten
dependency-cruiser configuration that covers the platform tiers only. Nothing runs the
generated one. On 23 September 2026 the same command on the same globs reported 78 errors
over 3,943 modules, 72 of them outside the six known exceptions. Most (53) are route
handlers importing server modules written after the doors were declared, such as the paywall
and the lesson-example loader. A declaration that nothing runs drifts like any other
document, which is the case for the last line of the checklist below.
Before you call it declared
- Three views, one source. The diagram, the doc table, and the checker config are all generated, and a test compares each with a fresh derivation.
- Allow, not deny. Every tier has an allow-list. Denies are for sentences that cut across tiers by file name or by package.
- Doors where there are internals. A tier with adapters, helpers, or a barrel names its entry points; nothing forbids a file by name.
- Exceptions expire. Every tolerated edge carries a reason and a date, and the date is enforced.
- It runs. The generated check is wired into CI, a hook, or the agent’s definition of done. A declaration nothing runs is a document again.
- The census was read by rule. A rule with dozens of edges is a tier declared wrong; a rule with one or two is a finding.
Know what this establishes. The declaration says which folder may know about which. It does not say the folders are the right ones, or that a permitted import is used well. A tier that may import everything is a declaration of nothing, and an allow-list written to match the tree as it is today merely photographs the drift. The moment to declare is when the picture on the wall still means something.
What belongs in the declaration?
Decide whether the fix is a permission, a door, an exception, or a source of truth.
The diagram was drawn at kickoff. The doc was updated when a tier was renamed. The checker config was edited when a violation was inconvenient. An agent reads the doc.
Leave the decisions next to the data.
- Tiers
- Each tier’s job in a sentence, and why the arrow points the way it does.
- Doors and shared
- Which tiers have entry points, and what everyone may import without asking.
- Census
- The first count, what each large rule turned out to be, and what remained.
- Exceptions
- Each tolerated edge, its reason, its date, and who decides.
- Derived
- Which files are generated, the command that regenerates them, and the test.
Put the note in the file the agent reads before it starts, and link the declaration. Once the generated check runs where nobody can skip it, an agent that reads the declaration reads the architecture that is enforced.
A declaration whose census you have read by rule, the tolerated edges written down as dated exceptions, and the generated check wired where it runs on every change.
Explain the declaration without saying “architecture as rules.”
“There is one file that lists our folders, what each may import, and the few places another folder may reach into. The diagram, the doc, and the checker are printed from it, and a test fails if any of them is edited by hand.” That is the technique. The name is what you call it in a review.
Before moving on, explain three things without the name: why an allow-list finds edges a deny-list never will, why a door survives a new adapter where a per-file rule does not, and why a first run of eighty-two edges is a census rather than eighty-two defects.
Connections to follow nextRelated lessons
- Enforcement layer runs rules like these where nobody can skip them: baseline, catch, wire.
- Spec before code gives the tests the same treatment: a source of truth written before the code, outside the author.
- Hexagonal / ports & adapters asks which way the arrows should point inside one application.
- Layered architecture is the shape most first declarations describe, tiers in a line.
- Modular monolith is where isolated groups earn their keep: modules that may not reach into each other.