| Clients import leaves, contracts or types only | ADR-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 header | ADR-0011 | [test] scripts/leaf-modules.test.mjs |
| Package exports are enumerated and documented | ADR-0002, ADR-0011 | [test] packages/api/src/exports.test.ts · [test] the package-exports check in apps/web/src/lib/docs/source.test.ts |
| Mobile aliases point at the canonical file; the web app does not use them | ADR-0011 | [test] packages/api/src/exports.test.ts (a test, not lint: see the note below) |
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.db | ADR-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 moves | ADR-0009 | [deploy] db:verify-rls at the end of db:apply-pending |
| Cross-tenant paths are listed | ADR-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 withTenant | AGENTS.md | [convention] |
| Isolation holds on a real deploy | ADR-0009 | [manual] scripts/rls-preview-proof.mjs against the preview |
| Hyperdrive query caching stays off | ADR-0009 | [deploy] packages/project-setup-cli/src/hyperdrive.mjs check in .github/workflows/deploy-prod.yml |
TENANCY_PROBE only on previews | ADR-0009 | [deploy] scripts/check-production-config.mjs |
| Domain failures are tagged and mapped once | ADR-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 language | error 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 client | ADR-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 drains | ADR-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 runtime | ADR-0005, ADR-0010 | [convention] |
| Only idempotent reads retry | ADR-0005 | [test] packages/api/src/platform/effect/retry.test.ts · [convention] for new call sites |
| Deadlines use production values | ADR-0005 | [test] @effect/vitest with TestClock, e.g. documents.jobs.test.ts, imports.jobs.test.ts |
| Queue persistence changes pass the fault gate | ADR-0006 | [manual] scripts/test-persisted-queue-spike.mjs |
Configuration is declared in packages/env | Backend 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 value | Environment and secrets | [test] packages/env/src/runtime-flags.test.ts |
Every RPC response carries x-request-id, and the error log records the ID | Error 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_VALIDATION | Error 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 orgProcedure | Backend 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 errors | Backend conventions | [test] packages/errors/src/server.test.ts |
| Schema changes are migrations, never pushes | Database | [tool] db:push runs packages/db/scripts/refuse-push.mjs · [test] CI applies migrations twice |
| Optional features can be removed cleanly | Optional features | [manual] scripts/verify-optional-client-removal.mjs |
| Dependencies respect the release quarantine and exact pins | Library reference | [test] scripts/tooling-tests.mjs |
| Formatting | Testing and verification | [lint] pnpm format:check (scripts/check-format.mjs) |
| Docs links, ADR pages and package exports pages are valid | Writing documentation | [test] apps/web/src/lib/docs/source.test.ts |
| Runtime and bundle changes are measured | Conventions | [convention] |
| Pagination strategy per endpoint | Backend conventions | [convention] |