Skip to content
Docs

Command palette

Jump to a page or view, or find a todo by name.

Changelog

Enforcement

Which rule is checked by what: type checks, lint, tests, build and deploy gates, scripts, and the rules that still rely on review.

Markdown

A rule that nothing checks decays. This page lists the repository's rules, where each one is stated and what fails when it is broken. Use it to decide how much to trust a rule, and to find the next rule that should get a check.

Levels

LevelMeaning
[type]pnpm check-types rejects the violation.
[lint]pnpm lint (oxlint plus repository checks) rejects it.
[test]A test in pnpm test, the integration suite or the RLS matrix fails.
[build]pnpm --filter web build fails.
[deploy]A preview or production workflow refuses to publish.
[tool]A repository script refuses the action.
[manual]A verification script exists, but you run it yourself; CI does not.
[convention]Only review holds it. These are the fragile ones.

Coverage

RuleStated inEnforcement
Clients import leaves, contracts or types onlyADR-0002[lint] packages/config/oxlint-client-boundary.json (server-only specifiers, including @template-cloudflare/api/mcp/*), extended by apps/web/src, apps/mobile/src, packages/ui/src · [test] scripts/leaf-modules.test.mjs (D1) · [build] apps/web/scripts/assert-client-bundle-clean.mjs
A feature contract carries the LEAF MODULE headerADR-0011[test] scripts/leaf-modules.test.mjs
Package exports are enumerated and documentedADR-0002, ADR-0011[test] packages/api/src/exports.test.ts · [test] the package-exports check in apps/web/src/lib/docs/source.test.ts
Clients import canonical API subpaths; no ./routers/* subpath comes backADR-0013[test] packages/api/src/exports.test.ts · [type] a removed subpath fails tsc
Mobile layers, cross-feature edges, platform variants, the route list and data access only in hooksADR-0013[test] scripts/mobile-feature-boundaries.test.mjs, scripts/import-cycles.test.mjs (a test, not lint: see the note below) · [lint] max-lines in apps/mobile/src/.oxlintrc.json
Only the app router and the tool catalog import *.procedures (D3)ADR-0011[lint] .oxlintrc.json, override for packages/api/src/**
Only the job runner imports *.jobs (D4)ADR-0011[lint] .oxlintrc.json, override for packages/api/src/**
platform/** does not import features, except admin.roles and admin.api (D5)ADR-0011[lint] .oxlintrc.json, override for packages/api/src/platform/**
Features import each other through public roles or declared edges (D6)ADR-0011[test] scripts/feature-boundaries.test.mjs
No import cycles in packages/api/src and packages/db/src (D7)ADR-0011[test] scripts/import-cycles.test.mjs, run by pnpm test
Migrated procedures hold no queries (D8)ADR-0011[lint] .oxlintrc.json, override listing todo and documents procedures: refuses drizzle-orm, the schema barrel @template-cloudflare/db/schema and @template-cloudflare/db/schema/<table> · [convention] context.db.query.*, which needs no import
Tenant data only through context.dbADR-0009[test] packages/api/src/platform/rpc/rls.isolation.test.ts, packages/db/src/rls-catalog.test.ts, test:rls-matrix (CI job rls-matrix)
RLS survives restores and host movesADR-0009[deploy] db:verify-rls at the end of db:apply-pending
Cross-tenant paths are listedADR-0009[lint] scripts/check-system-call-sites.mjs against packages/db/src/system-call-sites.txt; pool-import restriction in .oxlintrc.json
No network I/O inside withTenantAGENTS.md[convention]
Isolation holds on a real deployADR-0009[manual] scripts/rls-preview-proof.mjs against the preview
Hyperdrive query caching stays offADR-0009[deploy] packages/project-setup-cli/src/hyperdrive.mjs check in .github/workflows/deploy-prod.yml
TENANCY_PROBE only on previewsADR-0009[deploy] scripts/check-production-config.mjs
Domain failures are tagged and mapped onceADR-0010[type] the satisfies never mapper in each <feature>.errors.ts · [lint] D2 in .oxlintrc.json: no @orpc/* in services, rules and policies of migrated features · [test] packages/api/src/testing/characterization/*.client-errors.test.ts
Every error code has a message in every languageerror handling[type] satisfies in packages/errors/src/keys.ts · [test] packages/errors/src/locales.test.ts, apps/web/src/locales/locales.test.ts
Effect never reaches a clientADR-0005[build] apps/web/scripts/assert-client-bundle-clean.mjs
A procedure runs one program; a client abort after it starts does not roll back a write; database work drainsADR-0010[test] todo.abort.test.ts, documents.abort.test.ts, packages/api/src/platform/rpc/procedure-edge.test.ts, packages/api/src/platform/effect/db.test.ts
One run* per entry point, no module-scope I/O runtimeADR-0005, ADR-0010[convention]
Only idempotent reads retryADR-0005[test] packages/api/src/platform/effect/retry.test.ts · [convention] for new call sites
Deadlines use production valuesADR-0005[test] @effect/vitest with TestClock, e.g. documents.jobs.test.ts, imports.jobs.test.ts
Queue persistence changes pass the fault gateADR-0006[manual] scripts/test-persisted-queue-spike.mjs
Configuration is declared in packages/envBackend conventions[lint] node/no-process-env in .oxlintrc.json (packages/**) and apps/web/src/.oxlintrc.json, with named exceptions
Runtime switches fail safe on an invalid valueEnvironment and secrets[test] packages/env/src/runtime-flags.test.ts
Every RPC response carries x-request-id, and the error log records the IDError handling[test] packages/api/src/platform/rpc/request-id.test.ts, apps/web/src/routes/api/rpc/$.test.ts
Input validation answers HTTP 400 with INPUT_VALIDATIONError handling[test] packages/errors/src/server.test.ts (each APP_ERRORS status in errorStatusMap), apps/web/src/routes/api/rpc/$.test.ts (400 on the wire), characterization tests
Org role checks fail closed and only follow orgProcedureBackend conventions[type] the middleware's input context requires organizationRole · [test] packages/api/src/platform/rpc/require-org-role.test.ts (including @ts-expect-error on other procedures) · [convention] where it is applied
Expected failures are not logged as errorsBackend conventions[test] packages/errors/src/server.test.ts
Schema changes are migrations, never pushesDatabase[tool] db:push runs packages/db/scripts/refuse-push.mjs · [test] CI applies migrations twice
Optional features can be removed cleanlyOptional features[manual] scripts/verify-optional-client-removal.mjs
Dependencies respect the release quarantine and exact pinsLibrary reference[test] scripts/tooling-tests.mjs
FormattingTesting and verification[lint] pnpm format:check (scripts/check-format.mjs)
Docs links, ADR pages and package exports pages are validWriting documentation[test] apps/web/src/lib/docs/source.test.ts
Runtime and bundle changes are measuredConventions[convention]
Pagination strategy per endpointBackend conventions[convention]

Notes on the lint configuration

  • An oxlint override replaces a rule's options; it does not merge them. Every no-restricted-imports override in .oxlintrc.json therefore repeats the system-pool paths of the base rule, and the most specific files come last. A ban added in one override disappears for files a later override matches unless it is repeated there. For the same reason, the mobile app's direction rules are a test (scripts/mobile-feature-boundaries.test.mjs): as lint rules they would have to repeat the whole client boundary inside apps/mobile/src/.oxlintrc.json.
  • D2 and D8 list the migrated features by path. A feature that moves to tagged errors and an Effect service adds its *.service.ts, *.rules.ts, *.policy.ts and *.procedures.ts to those overrides in the same change. The comment above D2 names the features still to migrate; that list only shrinks.
  • Import cycles are a test, not a lint rule. import/no-cycle needs oxlint's import plugin, whose module graph made pnpm lint about 3.7 times slower. scripts/import-cycles.test.mjs runs the rule on the two server trees inside pnpm test.
  • Nothing in the root overrides matches apps/mobile, whose lint output stays unchanged.

Next rules to automate

  • No network I/O inside withTenant. A test could wrap the tenant handle and fail when a fetch starts inside the callback.
  • One run* per entry point. A lint pattern could refuse Effect.run* outside platform/rpc/procedure-edge.ts, platform/effect/edge.ts and the job runner.
  • Tenant isolation on a deploy. rls-preview-proof.mjs could run in the preview workflow after the runtime check.
  • Optional removal. The six removal runs could become a scheduled CI job. Today the base run and --remove-server-billing pass; the MCP and task combinations stop at packages/api type errors that wait on a product decision about those recipes.
Edit on GitHubDocumentation is in English.