# Architecture enforcement

`pnpm verify` runs `pnpm check:architecture` against the checkout and
`pnpm test:architecture` against isolated filesystem/package fixtures, after the
strict format/lint/type/build/baseline/rule checks. All authored JS/TS variants
are scanned. Prototypes receive the same checks.

## One explicit policy

`tooling/architecture/policy.mts` describes actual module roots, names, source
entries, exact built export maps, private trees, permitted dependency directions,
runtime environments, and dependencies that must load on demand. The lint rule
and graph share this policy and the TypeScript resolver. Future foundation paths
remain illustrative; the manifest does not require empty packages.

The current direction is embedding -> reporting/capture and reporting -> capture.
Capture cannot depend on reporting, including its UI. Embedding must defer capture
and reporting/browser. This encodes ownership/loading constraints, not product
interfaces or an obligation to add dependencies to the empty packages.

Support trees currently contain docs and tooling. New root files, support trees
(for example examples or CI), extensions, and conventional names require explicit
registration. Support trees do not waive naming, package registration, imports, or
cycles. Packages anywhere require a module registration. Only listed generated
and dependency directories, Git metadata, and TypeScript build info are excluded.

## Paths and exports

Use lowercase kebab-case directory/file basenames, with `.test` or `.spec` for
tests. Registered tool suffixes include `.config`, `.build`, `.browser`, `.base`,
and `.d`. Catalog tests use `.snag.md` and may start with a numeric filename prefix for deterministic ordering. README.md, AGENTS.md, GLOSSARY.md, SKILL.md and existing dotfiles are
explicit exceptions. Unknown dotfiles and suffixes fail. React symbol names may
remain PascalCase. Symlinked authored source is rejected to avoid hidden ownership.

JavaScript exports must match registered `types` and `import` targets exactly,
without extra subpaths or conditions. Optional CSS entries use exact registered
relative `.css` string targets, without extra conditions; their source entries
still count as UI for runtime boundary enforcement. Each source entry must exist
with exact case. External
callers must resolve to supported source entries. Package-name deep imports fail
even if they target public source. Relative imports to public source and
same-module private imports are valid.

Resolution uses actual files, inherited tsconfig aliases, and workspace entries.
Aliases take precedence when they redirect workspace names. Slash styles are
normalized; resolved spelling is checked against the filesystem, including on
case-insensitive Windows. Invalid/unresolved local paths fail. Lint alone is not
the unresolved-import check.

## Dependencies and loading

Static imports, re-exports, import types, import-equals, literal `import()` and
`require()` contribute edges. Computed arguments fail: use a literal. The one
registered exception is the deferred trusted project-module loader in
`tooling/testing/plan.mts`, using the exact `projectModuleUrl` identifier. It is
restricted to that tooling path; eager imports, `require()`, different identifiers,
and module-source exceptions still fail. Project modules execute trusted local
code and are outside the workspace dependency graph. File and
module cycles include type/deferred edges; module cycles also include workspace
dependencies declared in package manifests.

Runtime traversal starts at every supported entry and follows transitive helpers.
Declaration-level `import type` / `export type` edges load nothing. With the
required `verbatimModuleSyntax` setting, inline `import { type View }` and
`export { type View }` retain empty runtime declarations and eagerly evaluate
the target module, so they are runtime edges. Static imports and top-level dynamic imports,
including immediately invoked functions, are eager. Imports in other functions
are deferred. Deferred edges allow portable entries to reach optional browser/UI
code; top-level `import()` does not. Portable/browser/UI entries cannot reach
server code even lazily; server entries cannot reach browser/UI code. CSS and JSX
source count as UI.

Classify external runtime dependencies in `externalEnvironments` when adding them.
React/React DOM are UI; Node built-ins are server-only. Unknown external runtime
dependencies fail closed, including deferred ones. Longest matching module
`zones` refine the default environment, separating browser/UI source from portable
entries.

This source graph does not evaluate arbitrary JavaScript, infer calls to separately
declared functions at initialization, inspect external packages' transitive
contents, or interpret CSS imports/HTML script tags. Review initialization calls
and external classifications. Retain #26's built-consumer/selective-loading checks
for actual runtime and bundler behavior.

## Evolve the policy

Move source and update its root/entries/exports/private zones together. Register
new packages with their dependencies/environments, and update workspace,
TypeScript, build and lint configuration without relaxing strictness. Add path
exceptions individually. Do not allowlist a prototype or disable checks. Changes
to accepted foundation responsibilities need a recorded decision.

Add passing/failing isolated fixtures in `tooling/architecture/check.test.mts` for
each new category or meaningful regression. Tests create real package trees and
tsconfig files, assert diagnostics, and remove only their own fixtures. An actual
module move demonstrates policy evolution. Run `pnpm test:architecture`, then
`pnpm verify`. Diagnostics identify naming/layout, packages/exports, private and
unsupported imports, casing/resolution, dependency direction, computed imports,
runtime boundaries, eager optional dependencies, external classification, and
file/module cycles.
