Skip to Content
DevelopersCore conceptsGuardrails and sweeps

2 min read

Guardrails and sweeps

This page summarises the guardrail sweeps. The source of truth stays at docs/guardrails.md in the repo, next to the sweep source it documents.

bun run test:sweeps runs a set of vitest “sweeps” over the whole repo — static checks that catch drift across 1,400+ files, cheaper than a code review. CI runs them after bun run lint and bun run typecheck, before bun run test.

The ratchet pattern

Every sweep (except dead-code) is a ratchet: a typed ALLOWLIST next to it lists every violation that existed the day the sweep was added, each with a one-line reason. The sweep fails the moment someone adds a new violation that isn’t on the list.

  • Fix a violation by removing its allow-list entry.
  • Allow-list a new, deliberate exception only with a real one-line reason — not just to make the sweep pass.

The sweeps

SweepWhat it checks
service-layerDirect @commerce/db / drizzle-orm imports are only allowed in packages/core, packages/db, packages/api/src/services, scripts/.
client-imports-serverA 'use client' module must not value-import server-only packages (@commerce/core, @commerce/db, @commerce/auth, storage/payments/shipping connectors, @commerce/api).
no-orphansAn exported function/const/class in packages/*/src with no importer anywhere is an orphan.
dead-codeFixed list of paths/dependencies that were deliberately removed and must never come back — not a ratchet.
dangerous-htmlEvery dangerouslySetInnerHTML usage must be on the allow-list, with a reason it’s safe.
money-formatEvery Intl.NumberFormat(...) call site outside the canonical money module is on the allow-list.
hardcoded-coloursHex colour literals in UI .tsx files outside the allow-list — use CSS variables/tokens instead.
source-of-truth-headerNew files under packages/core/src and packages/plugins/src must start with a SOURCE OF TRUTH header comment.
pinned-actionsEvery uses: in .github/workflows is pinned to a full commit SHA with a # vX.Y.Z comment. No exceptions.
deploy-composedeploy/docker-compose.prod.yml passes every app setting from packages/config/src/env.ts to the apps (and deploy/env.production.example documents it), and only worker and backup get outbound internet besides the web-facing apps.
storefront-action-storeNo storefront server action takes a store id from its caller; the store comes from the hostname (requestStoreId).
turbo-cache-graphThe cached test/typecheck tasks see the plugin packages that core reaches through commerce.config.ts: the root //#plugin-graph task lists exactly what the root configs import, and every package built on core depends on it.
admin-routesNo new hardcoded admin path in an href or redirect/push/replace under apps/admin/src; use routes from lib/routes.ts. Allow-list is a count per file that may only go down.
site-configThe brand name appears only in siteConfig (@commerce/config/site), never as a literal elsewhere.

Cross-store leak test

packages/api/test/cross-store-leak.test.ts runs with the api suite (bun run test, needs Postgres). It seeds two stores with a row in every table that has a store_id (read from the schema, so new tables are covered), then, signed in as store A, calls every oRPC procedure and every storefront data loader with store A’s ids and again with store B’s ids. It fails if a response shows anything of store B, if any of B’s rows changed, or if A’s rows end up pointing at B’s. Procedures that call an outside service are skipped by name, with a reason. Details in docs/guardrails.md.

Running

Terminal
bun run test:sweeps bun run test:sweeps -- scripts/sweeps/service-layer.test.ts # one sweep

For the full write-up of each sweep, the fix/allow-list guidance, and the server-only package and router idiom notes, read docs/guardrails.md in the repo.

Was this helpful?