Skip to main content

B2B2C Architecture Contract V1

Use this as the non-negotiable contract for scaffolding, route boundaries, data boundaries, and feature growth.

For enforcement levels and rollout strategy, see:

Scope Model

Every request resolves one and only one runtime scope:

  • platform Global operator surface across all tenants.
  • org Tenant business operator surface.
  • customer End-customer portal surface.

Unknown scope fails closed.

Surface Boundaries

Treat each scope as a first-class product surface with explicit boundaries:

  • route boundary
  • data boundary
  • auth boundary
  • UI boundary

Do not collapse boundaries through convenience imports or shared ad-hoc logic.

Each surface may intentionally diverge in visual language and product experience.

  • platform, org, and portal may use different layouts, navigation models, density, motion, and theme treatment.
  • Shared primitives are opt-in infrastructure, not a requirement to make all surfaces look or behave the same.
  • Do not centralize page inventory just because two surfaces currently look similar.

Source Layout Contract

  • src/app/* Route entrypoints, layout composition, and route-owned page composition.
  • src/app/<surface>/**/_components/* Route-local page composition that belongs to one surface tree and is not intended for cross-surface reuse.
  • src/features/platform/* Platform-only feature modules.
  • src/features/org/* Org-admin feature modules.
  • src/features/customer/* Customer-portal feature modules.
  • src/core/* Shared infrastructure only (auth/session plumbing, host context, wrappers, design primitives).

src/core/* must not become a domain business-logic catch-all.

Feature Boundary Contract

  • Cross-feature imports must go through the target feature public entrypoint (index.ts).
  • Do not deep-import another feature's internals (components/*, lib/*, hooks/*, types/*).
  • Route files stay thin and only compose feature pages, access guards, and redirects.
  • src/components/* is for reusable surface composition and shared UI, not route-page inventory.
  • If page composition belongs to one route tree, keep it with that tree instead of promoting it into shared components prematurely.
  • Default to route-local ownership first. Promotion order is: route tree _components/* -> surface-local shared folder inside that route tree -> same-surface reusable component under src/components/<surface>/* when reuse escapes the tree -> cross-surface primitive under src/components/ui/* or src/lib/*.
  • Do not treat promoted component folders as catch-all storage for everything in a surface.

AuthZ Contract

  • Keep platform access separate from org access.
  • Keep org access separate from customer access.
  • Prefer capability checks over inline role branching.
  • Deny by default when membership or capability cannot be resolved.

Data Contract

  • Keep identity and memberships explicit.
  • Keep tenant ownership explicit on tenant-owned records.
  • Never rely on host inference alone for authorization decisions.
  • Use widen -> migrate -> narrow for breaking schema changes.

Convex Access Contract

  • UI pages/components call app wrapper modules, not generated Convex API paths directly.
  • Generated Convex API imports in src/* are allowed only in wrapper modules and proxy/middleware entrypoints.
  • Keep backend module names domain-specific, not generic.

Naming Contract

  • File and folder names: kebab-case.
  • React components: PascalCase.
  • Helpers/utilities/variables: camelCase.
  • Hooks: use*.
  • Constants: UPPER_SNAKE_CASE when truly constant.

Test Placement Contract

  • App unit tests live in apps/app/tests/* by default.
  • Do not scatter tests under src/* unless there is a strong colocated-testing reason.
  • Use *.test.ts and *.test.tsx naming.

Rule Gates For New Work

Before implementing a new feature, answer these in the design note or PR description:

  1. Which scope owns this feature (platform, org, or customer)?
  2. Which data boundary does it read/write?
  3. What capabilities are required?
  4. Does it cross feature boundaries through a public entrypoint?
  5. Does it require migration-safe schema rollout?

If any answer is unclear, pause and resolve design before implementation.