@wazir-dev/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +111 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +101 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/assets/composition-engine.mmd +34 -0
- package/assets/demo-script.sh +17 -0
- package/assets/logo-dark.svg +14 -0
- package/assets/logo.svg +14 -0
- package/assets/pipeline.mmd +39 -0
- package/assets/record-demo.sh +51 -0
- package/docs/README.md +51 -0
- package/docs/adapters/context-mode.md +60 -0
- package/docs/concepts/architecture.md +87 -0
- package/docs/concepts/artifact-model.md +60 -0
- package/docs/concepts/composition-engine.md +36 -0
- package/docs/concepts/indexing-and-recall.md +160 -0
- package/docs/concepts/observability.md +41 -0
- package/docs/concepts/roles-and-workflows.md +59 -0
- package/docs/concepts/terminology-policy.md +27 -0
- package/docs/getting-started/01-installation.md +78 -0
- package/docs/getting-started/02-first-run.md +102 -0
- package/docs/getting-started/03-adding-to-project.md +15 -0
- package/docs/getting-started/04-host-setup.md +15 -0
- package/docs/guides/ci-integration.md +15 -0
- package/docs/guides/creating-skills.md +15 -0
- package/docs/guides/expertise-module-authoring.md +15 -0
- package/docs/guides/hook-development.md +15 -0
- package/docs/guides/memory-and-learnings.md +34 -0
- package/docs/guides/multi-host-export.md +15 -0
- package/docs/guides/troubleshooting.md +101 -0
- package/docs/guides/writing-custom-roles.md +15 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
- package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
- package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
- package/docs/readmes/INDEX.md +99 -0
- package/docs/readmes/features/expertise/README.md +171 -0
- package/docs/readmes/features/exports/README.md +222 -0
- package/docs/readmes/features/hooks/README.md +103 -0
- package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
- package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
- package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
- package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
- package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
- package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
- package/docs/readmes/features/hooks/session-start.md +119 -0
- package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
- package/docs/readmes/features/roles/README.md +157 -0
- package/docs/readmes/features/roles/clarifier.md +152 -0
- package/docs/readmes/features/roles/content-author.md +190 -0
- package/docs/readmes/features/roles/designer.md +193 -0
- package/docs/readmes/features/roles/executor.md +184 -0
- package/docs/readmes/features/roles/learner.md +210 -0
- package/docs/readmes/features/roles/planner.md +182 -0
- package/docs/readmes/features/roles/researcher.md +164 -0
- package/docs/readmes/features/roles/reviewer.md +184 -0
- package/docs/readmes/features/roles/specifier.md +162 -0
- package/docs/readmes/features/roles/verifier.md +215 -0
- package/docs/readmes/features/schemas/README.md +178 -0
- package/docs/readmes/features/skills/README.md +63 -0
- package/docs/readmes/features/skills/brainstorming.md +96 -0
- package/docs/readmes/features/skills/debugging.md +148 -0
- package/docs/readmes/features/skills/design.md +120 -0
- package/docs/readmes/features/skills/prepare-next.md +109 -0
- package/docs/readmes/features/skills/run-audit.md +159 -0
- package/docs/readmes/features/skills/scan-project.md +109 -0
- package/docs/readmes/features/skills/self-audit.md +176 -0
- package/docs/readmes/features/skills/tdd.md +137 -0
- package/docs/readmes/features/skills/using-skills.md +92 -0
- package/docs/readmes/features/skills/verification.md +120 -0
- package/docs/readmes/features/skills/writing-plans.md +104 -0
- package/docs/readmes/features/tooling/README.md +320 -0
- package/docs/readmes/features/workflows/README.md +186 -0
- package/docs/readmes/features/workflows/author.md +181 -0
- package/docs/readmes/features/workflows/clarify.md +154 -0
- package/docs/readmes/features/workflows/design-review.md +171 -0
- package/docs/readmes/features/workflows/design.md +169 -0
- package/docs/readmes/features/workflows/discover.md +162 -0
- package/docs/readmes/features/workflows/execute.md +173 -0
- package/docs/readmes/features/workflows/learn.md +167 -0
- package/docs/readmes/features/workflows/plan-review.md +165 -0
- package/docs/readmes/features/workflows/plan.md +170 -0
- package/docs/readmes/features/workflows/prepare-next.md +167 -0
- package/docs/readmes/features/workflows/review.md +169 -0
- package/docs/readmes/features/workflows/run-audit.md +191 -0
- package/docs/readmes/features/workflows/spec-challenge.md +159 -0
- package/docs/readmes/features/workflows/specify.md +160 -0
- package/docs/readmes/features/workflows/verify.md +177 -0
- package/docs/readmes/packages/README.md +50 -0
- package/docs/readmes/packages/ajv.md +117 -0
- package/docs/readmes/packages/context-mode.md +118 -0
- package/docs/readmes/packages/gray-matter.md +116 -0
- package/docs/readmes/packages/node-test.md +137 -0
- package/docs/readmes/packages/yaml.md +112 -0
- package/docs/reference/configuration-reference.md +159 -0
- package/docs/reference/expertise-index.md +52 -0
- package/docs/reference/git-flow.md +43 -0
- package/docs/reference/hooks.md +87 -0
- package/docs/reference/host-exports.md +50 -0
- package/docs/reference/launch-checklist.md +172 -0
- package/docs/reference/marketplace-listings.md +76 -0
- package/docs/reference/release-process.md +34 -0
- package/docs/reference/roles-reference.md +77 -0
- package/docs/reference/skills.md +33 -0
- package/docs/reference/templates.md +29 -0
- package/docs/reference/tooling-cli.md +94 -0
- package/docs/truth-claims.yaml +222 -0
- package/expertise/PROGRESS.md +63 -0
- package/expertise/README.md +18 -0
- package/expertise/antipatterns/PROGRESS.md +56 -0
- package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
- package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
- package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
- package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
- package/expertise/antipatterns/backend/index.md +24 -0
- package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
- package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
- package/expertise/antipatterns/code/async-antipatterns.md +622 -0
- package/expertise/antipatterns/code/code-smells.md +1186 -0
- package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
- package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
- package/expertise/antipatterns/code/index.md +27 -0
- package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
- package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
- package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
- package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
- package/expertise/antipatterns/design/dark-patterns.md +1121 -0
- package/expertise/antipatterns/design/index.md +22 -0
- package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
- package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
- package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
- package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
- package/expertise/antipatterns/frontend/index.md +23 -0
- package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
- package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
- package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
- package/expertise/antipatterns/index.md +31 -0
- package/expertise/antipatterns/performance/index.md +20 -0
- package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
- package/expertise/antipatterns/performance/premature-optimization.md +623 -0
- package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
- package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
- package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
- package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
- package/expertise/antipatterns/process/index.md +23 -0
- package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
- package/expertise/antipatterns/security/index.md +20 -0
- package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
- package/expertise/antipatterns/security/security-theater.md +843 -0
- package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
- package/expertise/architecture/PROGRESS.md +70 -0
- package/expertise/architecture/data/caching-architecture.md +671 -0
- package/expertise/architecture/data/data-consistency.md +574 -0
- package/expertise/architecture/data/data-modeling.md +536 -0
- package/expertise/architecture/data/event-streams-and-queues.md +634 -0
- package/expertise/architecture/data/index.md +25 -0
- package/expertise/architecture/data/search-architecture.md +663 -0
- package/expertise/architecture/data/sql-vs-nosql.md +708 -0
- package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
- package/expertise/architecture/decisions/build-vs-buy.md +616 -0
- package/expertise/architecture/decisions/index.md +23 -0
- package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
- package/expertise/architecture/decisions/technology-selection.md +616 -0
- package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
- package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
- package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
- package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
- package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
- package/expertise/architecture/distributed/index.md +25 -0
- package/expertise/architecture/distributed/saga-pattern.md +797 -0
- package/expertise/architecture/foundations/architectural-thinking.md +460 -0
- package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
- package/expertise/architecture/foundations/design-principles-solid.md +649 -0
- package/expertise/architecture/foundations/domain-driven-design.md +719 -0
- package/expertise/architecture/foundations/index.md +25 -0
- package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
- package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
- package/expertise/architecture/index.md +34 -0
- package/expertise/architecture/integration/api-design-graphql.md +638 -0
- package/expertise/architecture/integration/api-design-grpc.md +804 -0
- package/expertise/architecture/integration/api-design-rest.md +892 -0
- package/expertise/architecture/integration/index.md +25 -0
- package/expertise/architecture/integration/third-party-integration.md +795 -0
- package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
- package/expertise/architecture/integration/websockets-realtime.md +791 -0
- package/expertise/architecture/mobile-architecture/index.md +22 -0
- package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
- package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
- package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
- package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
- package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
- package/expertise/architecture/patterns/event-driven.md +797 -0
- package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
- package/expertise/architecture/patterns/index.md +27 -0
- package/expertise/architecture/patterns/layered-architecture.md +736 -0
- package/expertise/architecture/patterns/microservices.md +753 -0
- package/expertise/architecture/patterns/modular-monolith.md +692 -0
- package/expertise/architecture/patterns/monolith.md +626 -0
- package/expertise/architecture/patterns/plugin-architecture.md +735 -0
- package/expertise/architecture/patterns/serverless.md +780 -0
- package/expertise/architecture/scaling/database-scaling.md +615 -0
- package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
- package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
- package/expertise/architecture/scaling/index.md +24 -0
- package/expertise/architecture/scaling/multi-tenancy.md +800 -0
- package/expertise/architecture/scaling/stateless-design.md +787 -0
- package/expertise/backend/embedded-firmware.md +625 -0
- package/expertise/backend/go.md +853 -0
- package/expertise/backend/index.md +24 -0
- package/expertise/backend/java-spring.md +448 -0
- package/expertise/backend/node-typescript.md +625 -0
- package/expertise/backend/python-fastapi.md +724 -0
- package/expertise/backend/rust.md +458 -0
- package/expertise/backend/solidity.md +711 -0
- package/expertise/composition-map.yaml +443 -0
- package/expertise/content/foundations/content-modeling.md +395 -0
- package/expertise/content/foundations/editorial-standards.md +449 -0
- package/expertise/content/foundations/index.md +24 -0
- package/expertise/content/foundations/microcopy.md +455 -0
- package/expertise/content/foundations/terminology-governance.md +509 -0
- package/expertise/content/index.md +34 -0
- package/expertise/content/patterns/accessibility-copy.md +518 -0
- package/expertise/content/patterns/index.md +24 -0
- package/expertise/content/patterns/notification-content.md +433 -0
- package/expertise/content/patterns/sample-content.md +486 -0
- package/expertise/content/patterns/state-copy.md +439 -0
- package/expertise/design/PROGRESS.md +58 -0
- package/expertise/design/disciplines/dark-mode-theming.md +577 -0
- package/expertise/design/disciplines/design-systems.md +595 -0
- package/expertise/design/disciplines/index.md +25 -0
- package/expertise/design/disciplines/information-architecture.md +800 -0
- package/expertise/design/disciplines/interaction-design.md +788 -0
- package/expertise/design/disciplines/responsive-design.md +552 -0
- package/expertise/design/disciplines/usability-testing.md +516 -0
- package/expertise/design/disciplines/user-research.md +792 -0
- package/expertise/design/foundations/accessibility-design.md +796 -0
- package/expertise/design/foundations/color-theory.md +797 -0
- package/expertise/design/foundations/iconography.md +795 -0
- package/expertise/design/foundations/index.md +26 -0
- package/expertise/design/foundations/motion-and-animation.md +653 -0
- package/expertise/design/foundations/rtl-design.md +585 -0
- package/expertise/design/foundations/spacing-and-layout.md +607 -0
- package/expertise/design/foundations/typography.md +800 -0
- package/expertise/design/foundations/visual-hierarchy.md +761 -0
- package/expertise/design/index.md +32 -0
- package/expertise/design/patterns/authentication-flows.md +474 -0
- package/expertise/design/patterns/content-consumption.md +789 -0
- package/expertise/design/patterns/data-display.md +618 -0
- package/expertise/design/patterns/e-commerce.md +1494 -0
- package/expertise/design/patterns/feedback-and-states.md +642 -0
- package/expertise/design/patterns/forms-and-input.md +819 -0
- package/expertise/design/patterns/gamification.md +801 -0
- package/expertise/design/patterns/index.md +31 -0
- package/expertise/design/patterns/microinteractions.md +449 -0
- package/expertise/design/patterns/navigation.md +800 -0
- package/expertise/design/patterns/notifications.md +705 -0
- package/expertise/design/patterns/onboarding.md +700 -0
- package/expertise/design/patterns/search-and-filter.md +601 -0
- package/expertise/design/patterns/settings-and-preferences.md +768 -0
- package/expertise/design/patterns/social-and-community.md +748 -0
- package/expertise/design/platforms/desktop-native.md +612 -0
- package/expertise/design/platforms/index.md +25 -0
- package/expertise/design/platforms/mobile-android.md +825 -0
- package/expertise/design/platforms/mobile-cross-platform.md +983 -0
- package/expertise/design/platforms/mobile-ios.md +699 -0
- package/expertise/design/platforms/tablet.md +794 -0
- package/expertise/design/platforms/web-dashboard.md +790 -0
- package/expertise/design/platforms/web-responsive.md +550 -0
- package/expertise/design/psychology/behavioral-nudges.md +449 -0
- package/expertise/design/psychology/cognitive-load.md +1191 -0
- package/expertise/design/psychology/error-psychology.md +778 -0
- package/expertise/design/psychology/index.md +22 -0
- package/expertise/design/psychology/persuasive-design.md +736 -0
- package/expertise/design/psychology/user-mental-models.md +623 -0
- package/expertise/design/tooling/open-pencil.md +266 -0
- package/expertise/frontend/angular.md +1073 -0
- package/expertise/frontend/desktop-electron.md +546 -0
- package/expertise/frontend/flutter.md +782 -0
- package/expertise/frontend/index.md +27 -0
- package/expertise/frontend/native-android.md +409 -0
- package/expertise/frontend/native-ios.md +490 -0
- package/expertise/frontend/react-native.md +1160 -0
- package/expertise/frontend/react.md +808 -0
- package/expertise/frontend/vue.md +1089 -0
- package/expertise/humanize/domain-rules-code.md +79 -0
- package/expertise/humanize/domain-rules-content.md +67 -0
- package/expertise/humanize/domain-rules-technical-docs.md +56 -0
- package/expertise/humanize/index.md +35 -0
- package/expertise/humanize/self-audit-checklist.md +87 -0
- package/expertise/humanize/sentence-patterns.md +218 -0
- package/expertise/humanize/vocabulary-blacklist.md +105 -0
- package/expertise/i18n/PROGRESS.md +65 -0
- package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
- package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
- package/expertise/i18n/advanced/complex-scripts.md +30 -0
- package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
- package/expertise/i18n/advanced/testing-i18n.md +28 -0
- package/expertise/i18n/content/content-adaptation.md +23 -0
- package/expertise/i18n/content/locale-specific-formatting.md +23 -0
- package/expertise/i18n/content/machine-translation-integration.md +28 -0
- package/expertise/i18n/content/translation-management.md +29 -0
- package/expertise/i18n/foundations/date-time-calendars.md +67 -0
- package/expertise/i18n/foundations/i18n-architecture.md +272 -0
- package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
- package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
- package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
- package/expertise/i18n/foundations/string-externalization.md +236 -0
- package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
- package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
- package/expertise/i18n/index.md +38 -0
- package/expertise/i18n/platform/backend-i18n.md +31 -0
- package/expertise/i18n/platform/flutter-i18n.md +148 -0
- package/expertise/i18n/platform/native-android-i18n.md +36 -0
- package/expertise/i18n/platform/native-ios-i18n.md +36 -0
- package/expertise/i18n/platform/react-i18n.md +103 -0
- package/expertise/i18n/platform/web-css-i18n.md +81 -0
- package/expertise/i18n/rtl/arabic-specific.md +175 -0
- package/expertise/i18n/rtl/hebrew-specific.md +149 -0
- package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
- package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
- package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
- package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
- package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
- package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
- package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
- package/expertise/i18n/rtl/rtl-typography.md +160 -0
- package/expertise/index.md +113 -0
- package/expertise/index.yaml +216 -0
- package/expertise/infrastructure/cloud-aws.md +597 -0
- package/expertise/infrastructure/cloud-gcp.md +599 -0
- package/expertise/infrastructure/cybersecurity.md +816 -0
- package/expertise/infrastructure/database-mongodb.md +447 -0
- package/expertise/infrastructure/database-postgres.md +400 -0
- package/expertise/infrastructure/devops-cicd.md +787 -0
- package/expertise/infrastructure/index.md +27 -0
- package/expertise/performance/PROGRESS.md +50 -0
- package/expertise/performance/backend/api-latency.md +1204 -0
- package/expertise/performance/backend/background-jobs.md +506 -0
- package/expertise/performance/backend/connection-pooling.md +1209 -0
- package/expertise/performance/backend/database-query-optimization.md +515 -0
- package/expertise/performance/backend/index.md +23 -0
- package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
- package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
- package/expertise/performance/foundations/caching-strategies.md +489 -0
- package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
- package/expertise/performance/foundations/index.md +24 -0
- package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
- package/expertise/performance/foundations/memory-management.md +964 -0
- package/expertise/performance/foundations/performance-budgets.md +1314 -0
- package/expertise/performance/index.md +31 -0
- package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
- package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
- package/expertise/performance/infrastructure/index.md +22 -0
- package/expertise/performance/infrastructure/load-balancing.md +1081 -0
- package/expertise/performance/infrastructure/observability.md +1079 -0
- package/expertise/performance/mobile/index.md +23 -0
- package/expertise/performance/mobile/mobile-animations.md +544 -0
- package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
- package/expertise/performance/mobile/mobile-network.md +452 -0
- package/expertise/performance/mobile/mobile-rendering.md +599 -0
- package/expertise/performance/mobile/mobile-startup-time.md +505 -0
- package/expertise/performance/platform-specific/flutter-performance.md +647 -0
- package/expertise/performance/platform-specific/index.md +22 -0
- package/expertise/performance/platform-specific/node-performance.md +1307 -0
- package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
- package/expertise/performance/platform-specific/react-performance.md +1403 -0
- package/expertise/performance/web/bundle-optimization.md +1239 -0
- package/expertise/performance/web/image-and-media.md +636 -0
- package/expertise/performance/web/index.md +24 -0
- package/expertise/performance/web/network-optimization.md +1133 -0
- package/expertise/performance/web/rendering-performance.md +1098 -0
- package/expertise/performance/web/ssr-and-hydration.md +918 -0
- package/expertise/performance/web/web-vitals.md +1374 -0
- package/expertise/quality/accessibility.md +985 -0
- package/expertise/quality/evidence-based-verification.md +499 -0
- package/expertise/quality/index.md +24 -0
- package/expertise/quality/ml-model-audit.md +614 -0
- package/expertise/quality/performance.md +600 -0
- package/expertise/quality/testing-api.md +891 -0
- package/expertise/quality/testing-mobile.md +496 -0
- package/expertise/quality/testing-web.md +849 -0
- package/expertise/security/PROGRESS.md +54 -0
- package/expertise/security/agentic-identity.md +540 -0
- package/expertise/security/compliance-frameworks.md +601 -0
- package/expertise/security/data/data-encryption.md +364 -0
- package/expertise/security/data/data-privacy-gdpr.md +692 -0
- package/expertise/security/data/database-security.md +1171 -0
- package/expertise/security/data/index.md +22 -0
- package/expertise/security/data/pii-handling.md +531 -0
- package/expertise/security/foundations/authentication.md +1041 -0
- package/expertise/security/foundations/authorization.md +603 -0
- package/expertise/security/foundations/cryptography.md +1001 -0
- package/expertise/security/foundations/index.md +25 -0
- package/expertise/security/foundations/owasp-top-10.md +1354 -0
- package/expertise/security/foundations/secrets-management.md +1217 -0
- package/expertise/security/foundations/secure-sdlc.md +700 -0
- package/expertise/security/foundations/supply-chain-security.md +698 -0
- package/expertise/security/index.md +31 -0
- package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
- package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
- package/expertise/security/infrastructure/container-security.md +721 -0
- package/expertise/security/infrastructure/incident-response.md +1295 -0
- package/expertise/security/infrastructure/index.md +24 -0
- package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
- package/expertise/security/infrastructure/network-security.md +1337 -0
- package/expertise/security/mobile/index.md +23 -0
- package/expertise/security/mobile/mobile-android-security.md +1218 -0
- package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
- package/expertise/security/mobile/mobile-data-storage.md +1265 -0
- package/expertise/security/mobile/mobile-ios-security.md +1401 -0
- package/expertise/security/mobile/mobile-network-security.md +1520 -0
- package/expertise/security/smart-contract-security.md +594 -0
- package/expertise/security/testing/index.md +22 -0
- package/expertise/security/testing/penetration-testing.md +1258 -0
- package/expertise/security/testing/security-code-review.md +1765 -0
- package/expertise/security/testing/threat-modeling.md +1074 -0
- package/expertise/security/testing/vulnerability-scanning.md +1062 -0
- package/expertise/security/web/api-security.md +586 -0
- package/expertise/security/web/cors-and-headers.md +433 -0
- package/expertise/security/web/csrf.md +562 -0
- package/expertise/security/web/file-upload.md +1477 -0
- package/expertise/security/web/index.md +25 -0
- package/expertise/security/web/injection.md +1375 -0
- package/expertise/security/web/session-management.md +1101 -0
- package/expertise/security/web/xss.md +1158 -0
- package/exports/README.md +17 -0
- package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
- package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
- package/exports/hosts/claude/.claude/agents/designer.md +55 -0
- package/exports/hosts/claude/.claude/agents/executor.md +55 -0
- package/exports/hosts/claude/.claude/agents/learner.md +51 -0
- package/exports/hosts/claude/.claude/agents/planner.md +53 -0
- package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
- package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
- package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
- package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
- package/exports/hosts/claude/.claude/commands/author.md +42 -0
- package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
- package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
- package/exports/hosts/claude/.claude/commands/design.md +44 -0
- package/exports/hosts/claude/.claude/commands/discover.md +37 -0
- package/exports/hosts/claude/.claude/commands/execute.md +48 -0
- package/exports/hosts/claude/.claude/commands/learn.md +38 -0
- package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
- package/exports/hosts/claude/.claude/commands/plan.md +39 -0
- package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
- package/exports/hosts/claude/.claude/commands/review.md +40 -0
- package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
- package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
- package/exports/hosts/claude/.claude/commands/specify.md +38 -0
- package/exports/hosts/claude/.claude/commands/verify.md +37 -0
- package/exports/hosts/claude/.claude/settings.json +34 -0
- package/exports/hosts/claude/CLAUDE.md +19 -0
- package/exports/hosts/claude/export.manifest.json +38 -0
- package/exports/hosts/claude/host-package.json +67 -0
- package/exports/hosts/codex/AGENTS.md +19 -0
- package/exports/hosts/codex/export.manifest.json +38 -0
- package/exports/hosts/codex/host-package.json +41 -0
- package/exports/hosts/cursor/.cursor/hooks.json +16 -0
- package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
- package/exports/hosts/cursor/export.manifest.json +38 -0
- package/exports/hosts/cursor/host-package.json +42 -0
- package/exports/hosts/gemini/GEMINI.md +19 -0
- package/exports/hosts/gemini/export.manifest.json +38 -0
- package/exports/hosts/gemini/host-package.json +41 -0
- package/hooks/README.md +18 -0
- package/hooks/definitions/loop_cap_guard.yaml +21 -0
- package/hooks/definitions/post_tool_capture.yaml +24 -0
- package/hooks/definitions/pre_compact_summary.yaml +19 -0
- package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
- package/hooks/definitions/protected_path_write_guard.yaml +19 -0
- package/hooks/definitions/session_start.yaml +19 -0
- package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
- package/hooks/loop-cap-guard +17 -0
- package/hooks/post-tool-lint +36 -0
- package/hooks/protected-path-write-guard +17 -0
- package/hooks/session-start +41 -0
- package/llms-full.txt +2355 -0
- package/llms.txt +43 -0
- package/package.json +79 -0
- package/roles/README.md +20 -0
- package/roles/clarifier.md +42 -0
- package/roles/content-author.md +63 -0
- package/roles/designer.md +55 -0
- package/roles/executor.md +55 -0
- package/roles/learner.md +51 -0
- package/roles/planner.md +53 -0
- package/roles/researcher.md +43 -0
- package/roles/reviewer.md +54 -0
- package/roles/specifier.md +47 -0
- package/roles/verifier.md +71 -0
- package/schemas/README.md +24 -0
- package/schemas/accepted-learning.schema.json +20 -0
- package/schemas/author-artifact.schema.json +156 -0
- package/schemas/clarification.schema.json +19 -0
- package/schemas/design-artifact.schema.json +80 -0
- package/schemas/docs-claim.schema.json +18 -0
- package/schemas/export-manifest.schema.json +20 -0
- package/schemas/hook.schema.json +67 -0
- package/schemas/host-export-package.schema.json +18 -0
- package/schemas/implementation-plan.schema.json +19 -0
- package/schemas/proposed-learning.schema.json +19 -0
- package/schemas/research.schema.json +18 -0
- package/schemas/review.schema.json +29 -0
- package/schemas/run-manifest.schema.json +18 -0
- package/schemas/spec-challenge.schema.json +18 -0
- package/schemas/spec.schema.json +20 -0
- package/schemas/usage.schema.json +102 -0
- package/schemas/verification-proof.schema.json +29 -0
- package/schemas/wazir-manifest.schema.json +173 -0
- package/skills/README.md +40 -0
- package/skills/brainstorming/SKILL.md +77 -0
- package/skills/debugging/SKILL.md +50 -0
- package/skills/design/SKILL.md +61 -0
- package/skills/dispatching-parallel-agents/SKILL.md +128 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +169 -0
- package/skills/humanize/SKILL.md +123 -0
- package/skills/init-pipeline/SKILL.md +124 -0
- package/skills/prepare-next/SKILL.md +20 -0
- package/skills/receiving-code-review/SKILL.md +123 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +108 -0
- package/skills/run-audit/SKILL.md +197 -0
- package/skills/scan-project/SKILL.md +41 -0
- package/skills/self-audit/SKILL.md +153 -0
- package/skills/subagent-driven-development/SKILL.md +154 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +102 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +23 -0
- package/skills/using-git-worktrees/SKILL.md +163 -0
- package/skills/using-skills/SKILL.md +95 -0
- package/skills/verification/SKILL.md +22 -0
- package/skills/wazir/SKILL.md +463 -0
- package/skills/writing-plans/SKILL.md +30 -0
- package/skills/writing-skills/SKILL.md +157 -0
- package/skills/writing-skills/anthropic-best-practices.md +122 -0
- package/skills/writing-skills/persuasion-principles.md +50 -0
- package/templates/README.md +20 -0
- package/templates/artifacts/README.md +10 -0
- package/templates/artifacts/accepted-learning.md +19 -0
- package/templates/artifacts/accepted-learning.template.json +12 -0
- package/templates/artifacts/author.md +74 -0
- package/templates/artifacts/author.template.json +19 -0
- package/templates/artifacts/clarification.md +21 -0
- package/templates/artifacts/clarification.template.json +12 -0
- package/templates/artifacts/execute-notes.md +19 -0
- package/templates/artifacts/implementation-plan.md +21 -0
- package/templates/artifacts/implementation-plan.template.json +11 -0
- package/templates/artifacts/learning-proposal.md +19 -0
- package/templates/artifacts/next-run-handoff.md +21 -0
- package/templates/artifacts/plan-review.md +19 -0
- package/templates/artifacts/proposed-learning.template.json +12 -0
- package/templates/artifacts/research.md +21 -0
- package/templates/artifacts/research.template.json +12 -0
- package/templates/artifacts/review-findings.md +19 -0
- package/templates/artifacts/review.template.json +11 -0
- package/templates/artifacts/run-manifest.template.json +8 -0
- package/templates/artifacts/spec-challenge.md +19 -0
- package/templates/artifacts/spec-challenge.template.json +11 -0
- package/templates/artifacts/spec.md +21 -0
- package/templates/artifacts/spec.template.json +12 -0
- package/templates/artifacts/verification-proof.md +19 -0
- package/templates/artifacts/verification-proof.template.json +11 -0
- package/templates/examples/accepted-learning.example.json +14 -0
- package/templates/examples/author.example.json +152 -0
- package/templates/examples/clarification.example.json +15 -0
- package/templates/examples/docs-claim.example.json +8 -0
- package/templates/examples/export-manifest.example.json +7 -0
- package/templates/examples/host-export-package.example.json +11 -0
- package/templates/examples/implementation-plan.example.json +17 -0
- package/templates/examples/proposed-learning.example.json +13 -0
- package/templates/examples/research.example.json +15 -0
- package/templates/examples/research.example.md +6 -0
- package/templates/examples/review.example.json +17 -0
- package/templates/examples/run-manifest.example.json +9 -0
- package/templates/examples/spec-challenge.example.json +14 -0
- package/templates/examples/spec.example.json +21 -0
- package/templates/examples/verification-proof.example.json +21 -0
- package/templates/examples/wazir-manifest.example.yaml +65 -0
- package/templates/task-definition-schema.md +99 -0
- package/tooling/README.md +20 -0
- package/tooling/src/adapters/context-mode.js +50 -0
- package/tooling/src/capture/command.js +376 -0
- package/tooling/src/capture/store.js +99 -0
- package/tooling/src/capture/usage.js +270 -0
- package/tooling/src/checks/branches.js +50 -0
- package/tooling/src/checks/brand-truth.js +110 -0
- package/tooling/src/checks/changelog.js +231 -0
- package/tooling/src/checks/command-registry.js +36 -0
- package/tooling/src/checks/commits.js +102 -0
- package/tooling/src/checks/docs-drift.js +103 -0
- package/tooling/src/checks/docs-truth.js +201 -0
- package/tooling/src/checks/runtime-surface.js +156 -0
- package/tooling/src/cli.js +116 -0
- package/tooling/src/command-options.js +56 -0
- package/tooling/src/commands/validate.js +320 -0
- package/tooling/src/doctor/command.js +91 -0
- package/tooling/src/export/command.js +77 -0
- package/tooling/src/export/compiler.js +498 -0
- package/tooling/src/guards/loop-cap-guard.js +52 -0
- package/tooling/src/guards/protected-path-write-guard.js +67 -0
- package/tooling/src/index/command.js +152 -0
- package/tooling/src/index/storage.js +1061 -0
- package/tooling/src/index/summarizers.js +261 -0
- package/tooling/src/loaders.js +18 -0
- package/tooling/src/project-root.js +22 -0
- package/tooling/src/recall/command.js +225 -0
- package/tooling/src/schema-validator.js +30 -0
- package/tooling/src/state-root.js +40 -0
- package/tooling/src/status/command.js +71 -0
- package/wazir.manifest.yaml +135 -0
- package/workflows/README.md +19 -0
- package/workflows/author.md +42 -0
- package/workflows/clarify.md +38 -0
- package/workflows/design-review.md +46 -0
- package/workflows/design.md +44 -0
- package/workflows/discover.md +37 -0
- package/workflows/execute.md +48 -0
- package/workflows/learn.md +38 -0
- package/workflows/plan-review.md +42 -0
- package/workflows/plan.md +39 -0
- package/workflows/prepare-next.md +37 -0
- package/workflows/review.md +40 -0
- package/workflows/run-audit.md +41 -0
- package/workflows/spec-challenge.md +41 -0
- package/workflows/specify.md +38 -0
- package/workflows/verify.md +37 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
# Multi-Tenancy — Architecture Expertise Module
|
|
2
|
+
|
|
3
|
+
> Multi-tenancy allows a single application instance to serve multiple customers (tenants) while keeping their data isolated. The key decision is the isolation level: shared database with tenant ID column (simple, dense), schema-per-tenant (moderate isolation), or database-per-tenant (maximum isolation, highest cost). Most SaaS applications should start with shared database + tenant ID.
|
|
4
|
+
|
|
5
|
+
> **Category:** Scaling
|
|
6
|
+
> **Complexity:** Complex
|
|
7
|
+
> **Applies when:** Building SaaS applications that serve multiple customers from the same infrastructure
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What This Is
|
|
12
|
+
|
|
13
|
+
Multi-tenancy is a software architecture in which a **single instance of an application serves multiple customers (tenants)**, each of whom perceives the system as their own private deployment. Tenants share compute, storage, and network infrastructure while their data, configurations, and customizations remain logically — and sometimes physically — separated.
|
|
14
|
+
|
|
15
|
+
The concept predates cloud computing. IBM mainframes in the 1960s time-shared resources across organizations. But the modern incarnation was shaped by two forces:
|
|
16
|
+
|
|
17
|
+
- **Salesforce (1999-present):** Pioneered the metadata-driven multi-tenant architecture where all tenants share a single database with a single schema. Customer data model changes are stored as rows in metadata tables — no `ALTER TABLE` needed. This architecture now serves over 150,000 organizations from the same codebase and database schema. It remains the most ambitious and successful shared-everything multi-tenant system ever built.
|
|
18
|
+
- **AWS and the cloud era (2006-present):** Made infrastructure elastic, enabling the hybrid models where most tenants share resources but enterprise customers receive dedicated infrastructure on demand.
|
|
19
|
+
|
|
20
|
+
### Single-Tenant vs Multi-Tenant
|
|
21
|
+
|
|
22
|
+
| Dimension | Single-Tenant | Multi-Tenant |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Infrastructure | Dedicated per customer | Shared across customers |
|
|
25
|
+
| Cost per customer | High (dedicated resources idle 80%+ of the time) | Low (resources pooled and utilized efficiently) |
|
|
26
|
+
| Deployment | One deployment per customer | One deployment serves all |
|
|
27
|
+
| Customization | Unlimited (separate codebase possible) | Bounded by configuration, feature flags, metadata |
|
|
28
|
+
| Upgrades | Per-customer rollout (slow, error-prone) | Single rollout for all tenants (fast, uniform) |
|
|
29
|
+
| Data isolation | Physical (different databases/servers) | Logical (tenant ID, RLS) or physical (per-tenant DB) |
|
|
30
|
+
| Operational burden | O(n) with customer count | O(1) — same system regardless of tenant count |
|
|
31
|
+
| Compliance | Simple (natural isolation) | Requires explicit isolation controls |
|
|
32
|
+
|
|
33
|
+
**The fundamental economics:** A single-tenant SaaS provider with 1,000 customers operates 1,000 deployments. Each must be monitored, patched, backed up, and upgraded independently. A multi-tenant provider with 1,000 customers operates one deployment. This is not a 2x or 5x difference — it is the difference between a company that can scale with a platform team of 5 and one that needs a platform team of 50.
|
|
34
|
+
|
|
35
|
+
### The Four Isolation Models
|
|
36
|
+
|
|
37
|
+
Multi-tenancy exists on a spectrum. The four canonical models, from least to most isolated:
|
|
38
|
+
|
|
39
|
+
**1. Shared-Everything (Single Database, Shared Schema)**
|
|
40
|
+
|
|
41
|
+
All tenants' data lives in the same tables, distinguished by a `tenant_id` column. The application filters every query by tenant. This is what Salesforce, Slack, Notion, and most modern SaaS applications use.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
┌─────────────────────────────────┐
|
|
45
|
+
│ Application │
|
|
46
|
+
│ (tenant context in request) │
|
|
47
|
+
├─────────────────────────────────┤
|
|
48
|
+
│ Shared Database │
|
|
49
|
+
│ ┌───────────────────────────┐ │
|
|
50
|
+
│ │ orders table │ │
|
|
51
|
+
│ │ tenant_id | order_id ... │ │
|
|
52
|
+
│ │ acme | 001 │ │
|
|
53
|
+
│ │ globex | 002 │ │
|
|
54
|
+
│ │ acme | 003 │ │
|
|
55
|
+
│ └───────────────────────────┘ │
|
|
56
|
+
└─────────────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**2. Shared Database, Separate Schema**
|
|
60
|
+
|
|
61
|
+
Each tenant gets their own schema within the same database instance. Tables are structurally identical but physically separate. Used by some Rails applications via the `apartment` gem and Django via `django-tenants`.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
┌─────────────────────────────────┐
|
|
65
|
+
│ Shared Database │
|
|
66
|
+
│ ┌────────────┐ ┌────────────┐ │
|
|
67
|
+
│ │ acme schema │ │globex │ │
|
|
68
|
+
│ │ orders │ │schema │ │
|
|
69
|
+
│ │ users │ │ orders │ │
|
|
70
|
+
│ │ products │ │ users │ │
|
|
71
|
+
│ └────────────┘ │ products │ │
|
|
72
|
+
│ └────────────┘ │
|
|
73
|
+
└─────────────────────────────────┘
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**3. Separate Database (Database-per-Tenant)**
|
|
77
|
+
|
|
78
|
+
Each tenant gets a dedicated database instance. The application routes connections based on tenant context. Provides strong isolation but linear cost scaling.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
┌──────────────┐ ┌──────────────┐
|
|
82
|
+
│ acme_db │ │ globex_db │
|
|
83
|
+
│ orders │ │ orders │
|
|
84
|
+
│ users │ │ users │
|
|
85
|
+
│ products │ │ products │
|
|
86
|
+
└──────────────┘ └──────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**4. Hybrid (Tiered Isolation)**
|
|
90
|
+
|
|
91
|
+
Most tenants share a pooled database, while enterprise tenants with compliance or performance requirements receive dedicated databases. This is the mature model used by most successful B2B SaaS companies at scale.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
┌──────────────────────┐ ┌──────────────┐
|
|
95
|
+
│ Shared Pool DB │ │ enterprise_db│
|
|
96
|
+
│ (free + standard │ │ (dedicated │
|
|
97
|
+
│ tier tenants) │ │ for BigCorp)│
|
|
98
|
+
└──────────────────────┘ └──────────────┘
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## When to Use Shared Database (tenant_id Column)
|
|
104
|
+
|
|
105
|
+
The shared database model is the correct starting point for the vast majority of SaaS applications. Use it when:
|
|
106
|
+
|
|
107
|
+
### Startups and early-stage products
|
|
108
|
+
|
|
109
|
+
Before you have 100 paying customers, the operational overhead of managing per-tenant databases is a distraction that provides no business value. Every hour spent on per-tenant infrastructure automation is an hour not spent on product-market fit.
|
|
110
|
+
|
|
111
|
+
**Slack** started with a shared PostgreSQL database and grew to millions of workspaces before needing to shard. The shared model got them through the critical growth phase without infrastructure complexity slowing feature velocity.
|
|
112
|
+
|
|
113
|
+
### Cost-sensitive SaaS (< $50/month price points)
|
|
114
|
+
|
|
115
|
+
When your average revenue per tenant is $20/month, a dedicated $50/month database instance per tenant is economically impossible. Shared databases let you serve 10,000 tenants from a single database that costs $500/month — $0.05 per tenant.
|
|
116
|
+
|
|
117
|
+
### Up to ~1,000 tenants with moderate data volumes
|
|
118
|
+
|
|
119
|
+
A well-indexed PostgreSQL database with Row Level Security can serve 1,000+ tenants with excellent performance as long as total data volume fits on a single instance (typically up to 1-4 TB with proper indexing). Beyond this, consider Citus for distributed multi-tenant PostgreSQL before jumping to per-tenant databases.
|
|
120
|
+
|
|
121
|
+
### Uniform schema requirements
|
|
122
|
+
|
|
123
|
+
When all tenants use the same data model — same tables, same columns, same relationships — schema-per-tenant and database-per-tenant add complexity without benefit. The shared model lets you add a column once and all tenants get it immediately.
|
|
124
|
+
|
|
125
|
+
### Fast tenant onboarding
|
|
126
|
+
|
|
127
|
+
In the shared model, creating a new tenant is an `INSERT INTO tenants` row. In database-per-tenant, it is provisioning a database, running migrations, configuring connection pooling, setting up monitoring, and updating routing tables. If your business depends on instant self-service signups, shared database removes an entire category of onboarding friction.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## When to Use Separate Database (Database-per-Tenant)
|
|
132
|
+
|
|
133
|
+
This model is justified under specific, concrete conditions — not as a default "enterprise-grade" choice.
|
|
134
|
+
|
|
135
|
+
### Regulatory or contractual data residency requirements
|
|
136
|
+
|
|
137
|
+
When enterprise customers contractually require their data to reside in a specific geographic region, or when regulations like GDPR, HIPAA, or SOC2 auditors specifically require physical data separation (not just logical isolation), a dedicated database in the required region is the cleanest compliance path.
|
|
138
|
+
|
|
139
|
+
**Example:** A healthcare SaaS serving hospitals in the EU and US may need patient data for German hospitals stored in Frankfurt and US hospital data in Virginia. Per-tenant databases in the appropriate region satisfy this cleanly.
|
|
140
|
+
|
|
141
|
+
### Very large tenants with asymmetric data volumes
|
|
142
|
+
|
|
143
|
+
When one tenant has 100x the data volume of an average tenant, they will dominate shared database resources — indexes, vacuum operations, buffer cache. A dedicated database prevents this tenant from degrading performance for all others.
|
|
144
|
+
|
|
145
|
+
**Example:** An analytics SaaS where most tenants have 10 GB of data but one enterprise customer has 2 TB. That single tenant would consume the majority of shared I/O bandwidth and make vacuum operations take hours.
|
|
146
|
+
|
|
147
|
+
### Tenant-specific backup and restore requirements
|
|
148
|
+
|
|
149
|
+
In a shared database, restoring a single tenant's data to a point in time requires surgically extracting their rows from a full database backup. With a dedicated database, you restore the entire database — a standard, well-tested operation that any DBA can perform under pressure at 3 AM.
|
|
150
|
+
|
|
151
|
+
### Tenants with custom schema extensions
|
|
152
|
+
|
|
153
|
+
If your product allows enterprise customers to define custom tables, custom columns, or stored procedures, a dedicated database prevents one tenant's schema experiments from affecting others' query performance.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## When NOT to Over-Isolate
|
|
158
|
+
|
|
159
|
+
**This section is deliberately as long as the sections above because over-isolation is the more common and more expensive mistake.** Teams default to database-per-tenant out of a vague sense that "more isolation is always better" without understanding the costs. It is architectural over-engineering with compounding operational consequences.
|
|
160
|
+
|
|
161
|
+
### Database-per-tenant at 10,000 tenants is an operational nightmare
|
|
162
|
+
|
|
163
|
+
Consider the concrete operational reality of managing 10,000 separate databases:
|
|
164
|
+
|
|
165
|
+
- **Schema migrations:** Every `ALTER TABLE` must run against 10,000 databases. If each migration takes 2 seconds (fast for a production database with locks), the total migration takes 5.5 hours. A failed migration on database #7,431 requires investigation and remediation while 2,569 databases are in the old schema and 7,431 are in the new one. Your application must handle both schema versions simultaneously.
|
|
166
|
+
- **Connection pooling:** Each database requires its own connection pool. At 10 connections per pool, you need 100,000 database connections. PgBouncer can help, but you are now operating PgBouncer as critical infrastructure managing 10,000 upstream databases.
|
|
167
|
+
- **Monitoring:** 10,000 databases means 10,000 sets of slow-query logs, 10,000 replication lag monitors, 10,000 disk-space alerts. Your monitoring system is now larger than most companies' entire infrastructure.
|
|
168
|
+
- **Backups:** 10,000 nightly backups, 10,000 backup verification jobs, 10,000 retention policies. One failed backup goes unnoticed, and that tenant's data is unrecoverable.
|
|
169
|
+
- **Cost:** Even at $10/month per managed database instance (the cheapest available), 10,000 databases cost $100,000/month just for database infrastructure. A single shared database handling the same workload might cost $2,000/month.
|
|
170
|
+
|
|
171
|
+
**Real-world cautionary tale:** Multiple SaaS companies have publicly discussed spending 6-12 months migrating from database-per-tenant back to shared databases after reaching the point where migration tooling consumed more engineering time than product development. The Atlas schema migration tool specifically addresses this pain point, but the tooling exists because the problem is so painful.
|
|
172
|
+
|
|
173
|
+
### Shared database with Row Level Security handles most compliance needs
|
|
174
|
+
|
|
175
|
+
Teams often choose database-per-tenant because "compliance requires data isolation." In practice, most compliance frameworks — SOC2, HIPAA, GDPR — require **access control**, not physical separation. Row Level Security in PostgreSQL provides database-enforced access control that satisfies auditors:
|
|
176
|
+
|
|
177
|
+
```sql
|
|
178
|
+
-- This policy is enforced by the database engine itself, not application code.
|
|
179
|
+
-- Even if application code has a bug, the database will not return
|
|
180
|
+
-- another tenant's data.
|
|
181
|
+
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
|
182
|
+
|
|
183
|
+
CREATE POLICY tenant_isolation ON orders
|
|
184
|
+
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
SOC2 auditors care that **no code path can access another tenant's data**, not whether data lives in separate physical databases. RLS provides this guarantee at the database layer, which is actually stronger than relying on application-level `WHERE tenant_id = ?` clauses that a developer might forget.
|
|
188
|
+
|
|
189
|
+
**PostgreSQL CVE note:** CVE-2024-10976 demonstrated that RLS policies could disregard user ID changes in certain edge cases, and CVE-2025-8713 revealed that optimizer statistics could leak sampled data from RLS-protected rows. These vulnerabilities were patched, but they underscore the importance of keeping PostgreSQL updated and having defense-in-depth (application-level filtering as a second layer).
|
|
190
|
+
|
|
191
|
+
### The hybrid model is almost always the right answer at scale
|
|
192
|
+
|
|
193
|
+
Instead of choosing one isolation level for all tenants, successful SaaS companies offer tiered isolation:
|
|
194
|
+
|
|
195
|
+
| Tier | Isolation Level | Price Point | Target Customer |
|
|
196
|
+
|---|---|---|---|
|
|
197
|
+
| Free / Starter | Shared DB, shared schema | $0-29/mo | Individual users, small teams |
|
|
198
|
+
| Professional | Shared DB, RLS-enforced | $30-299/mo | SMBs |
|
|
199
|
+
| Enterprise | Dedicated database, optional dedicated compute | $300-5000+/mo | Enterprises with compliance needs |
|
|
200
|
+
|
|
201
|
+
This model aligns isolation cost with revenue. The enterprise customers who need and demand dedicated infrastructure pay the premium that funds it. The free-tier users who generate minimal revenue share infrastructure efficiently.
|
|
202
|
+
|
|
203
|
+
**Slack, Notion, Figma, and most successful B2B SaaS companies** use variants of this hybrid approach.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## How It Works
|
|
208
|
+
|
|
209
|
+
### Tenant Context Propagation
|
|
210
|
+
|
|
211
|
+
Every request in a multi-tenant system must carry tenant identity. The tenant context must be set **once at the edge** and propagated through every layer — never derived from user input deep in the stack.
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
Request → API Gateway → Middleware → Service → Database
|
|
215
|
+
│ │
|
|
216
|
+
│ └─ Sets tenant context:
|
|
217
|
+
│ SET app.current_tenant = 'acme-corp';
|
|
218
|
+
└─ Extracts tenant from:
|
|
219
|
+
- JWT claim (tenant_id)
|
|
220
|
+
- Subdomain (acme.app.com)
|
|
221
|
+
- API key lookup
|
|
222
|
+
- OAuth token introspection
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Implementation (Express.js middleware example):**
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// Tenant middleware — runs before any route handler
|
|
229
|
+
async function tenantMiddleware(req, res, next) {
|
|
230
|
+
// Extract tenant from JWT, subdomain, or API key
|
|
231
|
+
const tenantId = extractTenantId(req);
|
|
232
|
+
|
|
233
|
+
if (!tenantId) {
|
|
234
|
+
return res.status(401).json({ error: 'Tenant not identified' });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate tenant exists and is active
|
|
238
|
+
const tenant = await tenantRegistry.get(tenantId);
|
|
239
|
+
if (!tenant || tenant.status !== 'active') {
|
|
240
|
+
return res.status(403).json({ error: 'Tenant suspended or not found' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Attach to request context (available to all downstream handlers)
|
|
244
|
+
req.tenantId = tenantId;
|
|
245
|
+
req.tenantConfig = tenant.config;
|
|
246
|
+
|
|
247
|
+
// Set database-level context for RLS
|
|
248
|
+
await db.query("SET app.current_tenant = $1", [tenantId]);
|
|
249
|
+
|
|
250
|
+
next();
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Row Level Security in PostgreSQL
|
|
255
|
+
|
|
256
|
+
RLS is the single most important tool for multi-tenant data isolation in a shared database. It moves tenant filtering from application code (where developers can forget it) to the database engine (where it is enforced unconditionally).
|
|
257
|
+
|
|
258
|
+
**Step 1: Enable RLS on every tenant-scoped table**
|
|
259
|
+
|
|
260
|
+
```sql
|
|
261
|
+
-- Create the table with tenant_id as the first column
|
|
262
|
+
-- (first column in composite indexes for efficient filtering)
|
|
263
|
+
CREATE TABLE orders (
|
|
264
|
+
tenant_id UUID NOT NULL,
|
|
265
|
+
order_id UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
266
|
+
customer TEXT NOT NULL,
|
|
267
|
+
total NUMERIC(10,2) NOT NULL,
|
|
268
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
269
|
+
PRIMARY KEY (tenant_id, order_id)
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
-- Enable RLS (without this, policies are ignored)
|
|
273
|
+
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
|
274
|
+
|
|
275
|
+
-- Force RLS even for table owners (critical for security)
|
|
276
|
+
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
|
277
|
+
|
|
278
|
+
-- Create the isolation policy
|
|
279
|
+
CREATE POLICY tenant_isolation_policy ON orders
|
|
280
|
+
FOR ALL
|
|
281
|
+
USING (tenant_id = current_setting('app.current_tenant')::uuid)
|
|
282
|
+
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Step 2: Set tenant context per connection/transaction**
|
|
286
|
+
|
|
287
|
+
```sql
|
|
288
|
+
-- At the start of every request, set the tenant context
|
|
289
|
+
-- This is typically done by middleware before any queries run
|
|
290
|
+
SET LOCAL app.current_tenant = 'a1b2c3d4-...';
|
|
291
|
+
|
|
292
|
+
-- Now all queries are automatically scoped:
|
|
293
|
+
SELECT * FROM orders;
|
|
294
|
+
-- Internally becomes: SELECT * FROM orders WHERE tenant_id = 'a1b2c3d4-...';
|
|
295
|
+
|
|
296
|
+
-- Inserts are also checked:
|
|
297
|
+
INSERT INTO orders (tenant_id, customer, total)
|
|
298
|
+
VALUES ('wrong-tenant-id', 'test', 100);
|
|
299
|
+
-- ERROR: new row violates row-level security policy
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Step 3: Create an application database role that respects RLS**
|
|
303
|
+
|
|
304
|
+
```sql
|
|
305
|
+
-- The application connects as this role, NOT as the table owner
|
|
306
|
+
CREATE ROLE app_user LOGIN PASSWORD '...';
|
|
307
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO app_user;
|
|
308
|
+
-- app_user is subject to RLS policies
|
|
309
|
+
-- The table owner (used only for migrations) bypasses RLS by default
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Query Scoping (Defense-in-Depth)
|
|
313
|
+
|
|
314
|
+
Even with RLS, application-level query scoping provides defense-in-depth. Use repository patterns or ORM scopes that automatically include tenant filtering:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
# Python/SQLAlchemy example: automatic tenant scoping
|
|
318
|
+
class TenantQuery(Query):
|
|
319
|
+
def get(self, *args, **kwargs):
|
|
320
|
+
# Automatically add tenant filter to every query
|
|
321
|
+
return super().filter(
|
|
322
|
+
self._entity.tenant_id == g.current_tenant_id
|
|
323
|
+
).get(*args, **kwargs)
|
|
324
|
+
|
|
325
|
+
class Order(Base):
|
|
326
|
+
__tablename__ = 'orders'
|
|
327
|
+
tenant_id = Column(UUID, nullable=False, index=True)
|
|
328
|
+
order_id = Column(UUID, primary_key=True)
|
|
329
|
+
# ...
|
|
330
|
+
|
|
331
|
+
query_class = TenantQuery # All queries auto-scoped
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Tenant-Aware Caching
|
|
335
|
+
|
|
336
|
+
Caching in a multi-tenant system requires tenant-scoped cache keys. A cache key of `orders:recent` is a cross-tenant data leak waiting to happen. Every cache key must include the tenant identifier.
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
# WRONG — cross-tenant data leak
|
|
340
|
+
cache.get("orders:recent")
|
|
341
|
+
|
|
342
|
+
# CORRECT — tenant-scoped cache key
|
|
343
|
+
cache.get(f"tenant:{tenant_id}:orders:recent")
|
|
344
|
+
|
|
345
|
+
# For Redis, use key prefixes or separate databases per tier:
|
|
346
|
+
# Free tier: redis DB 0, keys prefixed with tenant_id
|
|
347
|
+
# Enterprise: dedicated Redis instance per tenant
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Cache invalidation** also becomes tenant-scoped. When tenant A's data changes, only tenant A's cache entries should be invalidated — not the entire cache.
|
|
351
|
+
|
|
352
|
+
### Tenant-Aware Background Jobs
|
|
353
|
+
|
|
354
|
+
Background jobs (email sending, report generation, data exports) must carry tenant context explicitly. A job that loses its tenant context will either fail or — worse — operate on the wrong tenant's data.
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
# Enqueue with tenant context
|
|
358
|
+
queue.enqueue(
|
|
359
|
+
'generate_report',
|
|
360
|
+
tenant_id='acme-corp',
|
|
361
|
+
report_type='monthly_sales',
|
|
362
|
+
month='2025-01'
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Worker sets tenant context before execution
|
|
366
|
+
def process_job(job):
|
|
367
|
+
tenant_id = job.data['tenant_id']
|
|
368
|
+
set_tenant_context(tenant_id) # Sets DB session, cache prefix, etc.
|
|
369
|
+
try:
|
|
370
|
+
execute_job(job)
|
|
371
|
+
finally:
|
|
372
|
+
clear_tenant_context() # Prevent context leaking to next job
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Data Migration Across Isolation Levels
|
|
376
|
+
|
|
377
|
+
A well-designed multi-tenant system supports migrating a tenant between isolation levels — typically from shared to dedicated when they upgrade to enterprise tier.
|
|
378
|
+
|
|
379
|
+
**Migration steps (shared DB to dedicated DB):**
|
|
380
|
+
|
|
381
|
+
1. Provision new dedicated database, run schema migrations
|
|
382
|
+
2. Begin dual-write: all writes go to both shared and dedicated DB
|
|
383
|
+
3. Backfill: copy historical data from shared DB to dedicated DB
|
|
384
|
+
4. Verify: compare row counts and checksums between databases
|
|
385
|
+
5. Switch reads: point tenant's read path to dedicated DB
|
|
386
|
+
6. Stop dual-write to shared DB
|
|
387
|
+
7. Archive and delete tenant's rows from shared DB
|
|
388
|
+
|
|
389
|
+
This process should be automated and tested regularly. If it is a manual runbook, it will fail under pressure.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Trade-Offs Matrix
|
|
394
|
+
|
|
395
|
+
| Dimension | Shared DB + tenant_id | Schema-per-Tenant | Database-per-Tenant |
|
|
396
|
+
|---|---|---|---|
|
|
397
|
+
| **Tenant onboarding speed** | Instant (insert a row) | Seconds (create schema, run migrations) | Minutes to hours (provision DB, configure) |
|
|
398
|
+
| **Cost per tenant** | ~$0.01-0.10/mo (amortized) | ~$0.50-2/mo (schema overhead) | $10-200+/mo (dedicated instance) |
|
|
399
|
+
| **Data isolation** | Logical (application + RLS) | Moderate (schema boundary) | Physical (separate instance) |
|
|
400
|
+
| **Noisy neighbor risk** | High (shared I/O, CPU, memory) | Medium (shared I/O, separate tables) | None (fully isolated resources) |
|
|
401
|
+
| **Schema migration speed** | Fast (one ALTER TABLE) | Slow (N schemas x migration) | Very slow (N databases x migration) |
|
|
402
|
+
| **Tenant-specific schema** | Not possible | Possible but complicates migrations | Fully flexible |
|
|
403
|
+
| **Backup/restore granularity** | Difficult (extract rows from full backup) | Moderate (pg_dump single schema) | Easy (standard DB restore) |
|
|
404
|
+
| **Cross-tenant queries** | Easy (single query, no tenant filter) | Moderate (cross-schema joins) | Very difficult (federated queries) |
|
|
405
|
+
| **Connection pooling** | Simple (one pool) | Moderate (one pool, schema switching) | Complex (N pools or N routing rules) |
|
|
406
|
+
| **Monitoring complexity** | Simple (one database to monitor) | Moderate (per-schema metrics) | High (N databases to monitor) |
|
|
407
|
+
| **Maximum practical tenants** | 100,000+ | ~5,000 (schema creation overhead) | ~500-1,000 (operational ceiling) |
|
|
408
|
+
| **Compliance satisfaction** | RLS satisfies most auditors | Stronger isolation for stricter audits | Satisfies all compliance requirements |
|
|
409
|
+
| **Operational team size needed** | Small (1-2 DBAs) | Medium (2-3 DBAs) | Large (3-5+ DBAs or heavy automation) |
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Evolution Path
|
|
414
|
+
|
|
415
|
+
The most successful multi-tenant architectures evolve through stages, driven by concrete business needs — not speculative "we might need this."
|
|
416
|
+
|
|
417
|
+
### Stage 1: Shared Database (Day 1 — ~1,000 tenants)
|
|
418
|
+
|
|
419
|
+
Start here. Every table has a `tenant_id` column. Enable RLS from day one — it is far easier to enable on empty tables than to retrofit. Build your ORM/repository layer with automatic tenant scoping.
|
|
420
|
+
|
|
421
|
+
**Key discipline:** Include `tenant_id` in every primary key and every foreign key as a composite. This makes future sharding possible without restructuring your data model.
|
|
422
|
+
|
|
423
|
+
```sql
|
|
424
|
+
-- DO THIS from day one
|
|
425
|
+
PRIMARY KEY (tenant_id, order_id)
|
|
426
|
+
FOREIGN KEY (tenant_id, customer_id) REFERENCES customers (tenant_id, customer_id)
|
|
427
|
+
|
|
428
|
+
-- NOT THIS — makes future sharding extremely painful
|
|
429
|
+
PRIMARY KEY (order_id)
|
|
430
|
+
FOREIGN KEY (customer_id) REFERENCES customers (customer_id)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Stage 2: Shared Database + Noisy Neighbor Controls (~1,000-10,000 tenants)
|
|
434
|
+
|
|
435
|
+
Add per-tenant rate limiting, query timeouts, and resource quotas. Monitor per-tenant resource consumption. Identify tenants whose usage patterns degrade the experience for others.
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
# Per-tenant rate limiting
|
|
439
|
+
@rate_limit(
|
|
440
|
+
key=lambda req: req.tenant_id,
|
|
441
|
+
limits={
|
|
442
|
+
'free': '100/minute',
|
|
443
|
+
'pro': '1000/minute',
|
|
444
|
+
'enterprise': '10000/minute'
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
def api_handler(req):
|
|
448
|
+
...
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Stage 3: Hybrid — Selective Isolation for Enterprise (~5,000+ tenants)
|
|
452
|
+
|
|
453
|
+
When you sign enterprise customers with compliance requirements or data volumes that degrade shared infrastructure, offer dedicated databases as a premium tier. Build the migration tooling (Stage 1 → Stage 3) as a product feature, not an ad-hoc project.
|
|
454
|
+
|
|
455
|
+
### Stage 4: Distributed Multi-Tenant (Citus/Sharding) (~50,000+ tenants)
|
|
456
|
+
|
|
457
|
+
When a single PostgreSQL instance cannot handle the total data volume or query load, use Citus to distribute the shared database across multiple nodes while maintaining the shared-schema model. Tenant data is automatically co-located on the same node, preserving efficient joins.
|
|
458
|
+
|
|
459
|
+
```sql
|
|
460
|
+
-- Citus: distribute tables by tenant_id
|
|
461
|
+
SELECT create_distributed_table('orders', 'tenant_id');
|
|
462
|
+
SELECT create_distributed_table('order_items', 'tenant_id');
|
|
463
|
+
-- Queries filtered by tenant_id route to a single node
|
|
464
|
+
-- Cross-tenant aggregations still work (distributed query engine)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Stage 5: Cell-Based Architecture (~100,000+ tenants)
|
|
468
|
+
|
|
469
|
+
At massive scale, partition tenants into cells — self-contained units of infrastructure that each serve a subset of tenants. Each cell is a complete stack (application, database, cache, queue). New tenants are assigned to cells with capacity. Failing cells affect only their tenants, not the entire platform.
|
|
470
|
+
|
|
471
|
+
AWS presented this as "SaaS meets cell-based architecture" at re:Invent 2024, describing it as a natural fit for multi-tenant systems at hyperscale.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Failure Modes
|
|
476
|
+
|
|
477
|
+
### Data Leakage Between Tenants — The Worst Possible Bug
|
|
478
|
+
|
|
479
|
+
Cross-tenant data leakage is the most severe bug a multi-tenant system can have. It is worse than downtime. It is worse than data loss. A single incident destroys customer trust and can trigger regulatory action.
|
|
480
|
+
|
|
481
|
+
**How it happens:**
|
|
482
|
+
|
|
483
|
+
1. **Missing WHERE clause:** A developer writes `SELECT * FROM orders` without the tenant filter. In testing (single-tenant test database), this works perfectly. In production, it returns all tenants' orders.
|
|
484
|
+
2. **Cached data served to wrong tenant:** A cache key without tenant scoping (`cache.get("dashboard_data")`) returns tenant A's dashboard to tenant B.
|
|
485
|
+
3. **Background job loses tenant context:** A job is enqueued with tenant context but the worker does not set the database session variable before executing. Queries run without RLS filtering.
|
|
486
|
+
4. **API endpoint missing tenant middleware:** A new endpoint is added without the tenant authentication middleware. Requests without a valid tenant token access an unscoped database view.
|
|
487
|
+
5. **ORM eager loading bypasses RLS:** Some ORMs, when performing eager/lazy loading of associations, issue separate queries that may not carry the tenant context set for the parent query.
|
|
488
|
+
|
|
489
|
+
**Real incidents:**
|
|
490
|
+
- A 2021 Microsoft Azure vulnerability ("ChaosDB") allowed attackers to access other customers' Cosmos DB instances through a misconfigured Jupyter notebook feature.
|
|
491
|
+
- AWS AppSync had a cross-tenant vulnerability allowing access to resources in other organizations' accounts.
|
|
492
|
+
- Multiple SaaS companies have disclosed incidents where API endpoints returned data belonging to other tenants due to missing tenant filtering in new code paths.
|
|
493
|
+
|
|
494
|
+
**Prevention:**
|
|
495
|
+
- RLS as the primary barrier (database-enforced, not application-enforced)
|
|
496
|
+
- Application-level tenant scoping as defense-in-depth
|
|
497
|
+
- Integration tests that create two tenants, insert data for both, and verify each tenant can only see their own data — run these tests on every deployment
|
|
498
|
+
- Code review checklists that specifically verify tenant filtering on every new query
|
|
499
|
+
- Static analysis rules that flag raw SQL without tenant_id references
|
|
500
|
+
|
|
501
|
+
### Noisy Neighbor
|
|
502
|
+
|
|
503
|
+
One tenant's heavy workload degrades performance for all tenants sharing the same infrastructure.
|
|
504
|
+
|
|
505
|
+
**Symptoms:** Spike in response times for all tenants correlated with one tenant's batch processing job, large data export, or traffic surge.
|
|
506
|
+
|
|
507
|
+
**Mitigations:**
|
|
508
|
+
- Per-tenant query timeouts: `SET LOCAL statement_timeout = '5s';`
|
|
509
|
+
- Per-tenant connection limits in the connection pool
|
|
510
|
+
- Per-tenant rate limiting at the API gateway
|
|
511
|
+
- Resource quotas (CPU, memory) via Kubernetes resource limits for tenant-specific workloads
|
|
512
|
+
- Separate queues for background jobs by tenant tier
|
|
513
|
+
- Move consistently noisy tenants to dedicated infrastructure (and charge accordingly)
|
|
514
|
+
|
|
515
|
+
### Tenant-Specific Outage Cascading
|
|
516
|
+
|
|
517
|
+
A bug triggered by one tenant's specific data pattern crashes the application for all tenants.
|
|
518
|
+
|
|
519
|
+
**Example:** Tenant A uploads a 500 MB CSV file. The import handler loads it entirely into memory, OOM-kills the application process, and all tenants lose service.
|
|
520
|
+
|
|
521
|
+
**Mitigations:**
|
|
522
|
+
- Tenant-specific error isolation (catch and contain errors per-tenant request)
|
|
523
|
+
- Circuit breakers per tenant: if tenant A's requests fail 5 times consecutively, temporarily reject tenant A's requests while continuing to serve others
|
|
524
|
+
- Bulkhead pattern: separate thread/process pools for different tenants or tenant tiers
|
|
525
|
+
- Input validation with per-tenant resource limits (max file size, max rows per import, max API payload size)
|
|
526
|
+
|
|
527
|
+
### Migration Complexity
|
|
528
|
+
|
|
529
|
+
Moving a tenant between isolation levels (shared to dedicated, or between shards) risks data loss or inconsistency.
|
|
530
|
+
|
|
531
|
+
**Mitigations:**
|
|
532
|
+
- Dual-write during migration with reconciliation checks
|
|
533
|
+
- Automated migration tooling tested in staging with production-sized datasets
|
|
534
|
+
- Rollback capability: ability to move the tenant back to shared if dedicated has issues
|
|
535
|
+
- Migration as a product feature, not an operational procedure
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Technology Landscape
|
|
540
|
+
|
|
541
|
+
### PostgreSQL Row Level Security (RLS)
|
|
542
|
+
|
|
543
|
+
The most mature and widely used solution for shared-database multi-tenancy. Available since PostgreSQL 9.5 (2016).
|
|
544
|
+
|
|
545
|
+
**Strengths:**
|
|
546
|
+
- Policies enforced by the database engine — cannot be bypassed by application code (unless connecting as table owner)
|
|
547
|
+
- Works with standard PostgreSQL tooling (pg_dump, pg_restore, logical replication)
|
|
548
|
+
- Minimal performance overhead (typically < 5%) when policies use indexed columns
|
|
549
|
+
- Policies can be arbitrarily complex (not limited to tenant_id equality)
|
|
550
|
+
|
|
551
|
+
**Limitations:**
|
|
552
|
+
- RLS is bypassed by the table owner role and roles with BYPASSRLS attribute — ensure the application connects as a non-owner role
|
|
553
|
+
- Complex permission models (row-level + column-level + attribute-based) may outgrow RLS capabilities
|
|
554
|
+
- Debugging RLS issues requires careful logging since policy execution is not directly observable
|
|
555
|
+
- Must keep PostgreSQL patched — CVEs have specifically targeted RLS enforcement
|
|
556
|
+
|
|
557
|
+
### Schema-Based Multi-Tenancy Libraries
|
|
558
|
+
|
|
559
|
+
- **`django-tenants`** (Python/Django): Maps tenants to PostgreSQL schemas via `search_path`. Middleware automatically sets the schema based on the request's subdomain or header.
|
|
560
|
+
- **`apartment`** (Ruby/Rails): Schema-based tenant switching for ActiveRecord. Each tenant gets a separate PostgreSQL schema with identical table structures.
|
|
561
|
+
- **`spring-multitenancy`** and **Hibernate multi-tenancy**: Java ecosystem support for schema-per-tenant and database-per-tenant patterns with connection routing.
|
|
562
|
+
|
|
563
|
+
**Common issue with schema-based libraries:** Schema creation and migration across N schemas becomes slow. At 5,000 schemas, a migration that adds a column takes 5,000 `ALTER TABLE` statements. Tools like Atlas and Flyway have specific support for batch multi-schema migrations.
|
|
564
|
+
|
|
565
|
+
### Citus for Distributed Multi-Tenant PostgreSQL
|
|
566
|
+
|
|
567
|
+
Citus is an open-source PostgreSQL extension (now part of Azure Cosmos DB for PostgreSQL) that distributes tables across multiple nodes while preserving the PostgreSQL interface.
|
|
568
|
+
|
|
569
|
+
**How it works for multi-tenancy:**
|
|
570
|
+
- All tables are distributed by `tenant_id` (the distribution column)
|
|
571
|
+
- Rows for the same tenant are co-located on the same physical node
|
|
572
|
+
- Queries filtered by `tenant_id` route to a single node — same performance as standard PostgreSQL
|
|
573
|
+
- Cross-tenant aggregate queries (admin dashboards) use the distributed query engine
|
|
574
|
+
- Large tenants can be isolated on dedicated nodes using tenant placement controls
|
|
575
|
+
|
|
576
|
+
**Citus 12+ schema-based sharding:** As an alternative to row-based distribution, Citus 12 introduced schema-based sharding where each tenant gets a PostgreSQL schema that is automatically placed on a node. This requires minimal query changes (just set `search_path`) and suits applications that already use schema-per-tenant.
|
|
577
|
+
|
|
578
|
+
### Other Technologies
|
|
579
|
+
|
|
580
|
+
- **AWS RDS Proxy:** Connection pooling and routing for database-per-tenant architectures on AWS. Reduces the connection overhead of managing many databases.
|
|
581
|
+
- **ProxySQL / PgBouncer:** Connection poolers that can route queries to different backends based on tenant context.
|
|
582
|
+
- **Kubernetes Namespaces:** Provide compute-level isolation for tenants requiring dedicated application instances alongside dedicated databases.
|
|
583
|
+
- **HashiCorp Vault:** Manages per-tenant encryption keys, database credentials, and secrets in multi-tenant environments.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Decision Tree
|
|
588
|
+
|
|
589
|
+
```
|
|
590
|
+
Start: Building a SaaS application?
|
|
591
|
+
│
|
|
592
|
+
├─ YES: How many tenants do you expect in 2 years?
|
|
593
|
+
│ │
|
|
594
|
+
│ ├─ < 10 tenants, all enterprise, high-touch sales
|
|
595
|
+
│ │ └─ Consider single-tenant deployments
|
|
596
|
+
│ │ (operational overhead is manageable at this scale,
|
|
597
|
+
│ │ and enterprise customers may contractually require it)
|
|
598
|
+
│ │
|
|
599
|
+
│ ├─ 10-100,000 tenants, self-service signups
|
|
600
|
+
│ │ │
|
|
601
|
+
│ │ ├─ Do any tenants have regulatory requirements
|
|
602
|
+
│ │ │ for physical data separation?
|
|
603
|
+
│ │ │ │
|
|
604
|
+
│ │ │ ├─ NO → Shared DB + RLS
|
|
605
|
+
│ │ │ │ (simplest, cheapest, fastest to build)
|
|
606
|
+
│ │ │ │
|
|
607
|
+
│ │ │ └─ YES, but < 5% of tenants
|
|
608
|
+
│ │ │ └─ Hybrid: Shared DB for standard,
|
|
609
|
+
│ │ │ dedicated DB for enterprise
|
|
610
|
+
│ │ │
|
|
611
|
+
│ │ ├─ Will individual tenants have > 100 GB of data?
|
|
612
|
+
│ │ │ │
|
|
613
|
+
│ │ │ ├─ NO → Shared DB + RLS (handles this easily)
|
|
614
|
+
│ │ │ │
|
|
615
|
+
│ │ │ └─ YES → Hybrid or Citus
|
|
616
|
+
│ │ │ (co-locate large tenants on dedicated nodes)
|
|
617
|
+
│ │ │
|
|
618
|
+
│ │ └─ Do tenants need custom schema extensions?
|
|
619
|
+
│ │ │
|
|
620
|
+
│ │ ├─ NO → Shared DB + RLS
|
|
621
|
+
│ │ │
|
|
622
|
+
│ │ └─ YES → Schema-per-tenant or metadata-driven
|
|
623
|
+
│ │ (Salesforce-style: store custom fields as
|
|
624
|
+
│ │ metadata rows, not physical columns)
|
|
625
|
+
│ │
|
|
626
|
+
│ └─ > 100,000 tenants
|
|
627
|
+
│ └─ Shared DB + Citus distributed sharding
|
|
628
|
+
│ or cell-based architecture
|
|
629
|
+
│ (database-per-tenant is operationally impossible
|
|
630
|
+
│ at this scale)
|
|
631
|
+
│
|
|
632
|
+
└─ NO: Multi-tenancy is not relevant to your architecture.
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Implementation Sketch
|
|
638
|
+
|
|
639
|
+
### Complete PostgreSQL RLS Setup
|
|
640
|
+
|
|
641
|
+
```sql
|
|
642
|
+
-- 1. Tenants registry
|
|
643
|
+
CREATE TABLE tenants (
|
|
644
|
+
tenant_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
645
|
+
name TEXT NOT NULL, slug TEXT UNIQUE NOT NULL,
|
|
646
|
+
tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free','pro','enterprise')),
|
|
647
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','suspended','deleted')),
|
|
648
|
+
config JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
-- 2. Tenant-scoped table (composite PK is critical for future sharding)
|
|
652
|
+
CREATE TABLE orders (
|
|
653
|
+
tenant_id UUID NOT NULL REFERENCES tenants(tenant_id),
|
|
654
|
+
order_id UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
655
|
+
customer TEXT NOT NULL,
|
|
656
|
+
total NUMERIC(10,2) NOT NULL,
|
|
657
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
658
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
659
|
+
PRIMARY KEY (tenant_id, order_id)
|
|
660
|
+
);
|
|
661
|
+
CREATE INDEX idx_orders_tenant_created ON orders (tenant_id, created_at DESC);
|
|
662
|
+
|
|
663
|
+
-- 3. Enable RLS + single policy for all operations
|
|
664
|
+
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
|
665
|
+
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
|
666
|
+
|
|
667
|
+
CREATE POLICY tenant_isolation ON orders FOR ALL
|
|
668
|
+
USING (tenant_id = current_setting('app.current_tenant')::uuid)
|
|
669
|
+
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
670
|
+
|
|
671
|
+
-- 4. Application role (subject to RLS — NOT the table owner)
|
|
672
|
+
CREATE ROLE app_service LOGIN PASSWORD 'strong-password-here';
|
|
673
|
+
GRANT USAGE ON SCHEMA public TO app_service;
|
|
674
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO app_service;
|
|
675
|
+
|
|
676
|
+
-- 5. Admin role for cross-tenant reads (dashboards, support)
|
|
677
|
+
CREATE ROLE admin_service LOGIN PASSWORD 'different-strong-password';
|
|
678
|
+
GRANT SELECT ON ALL TABLES IN SCHEMA public TO admin_service;
|
|
679
|
+
CREATE POLICY admin_read ON orders FOR SELECT TO admin_service USING (true);
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Tenant Middleware and Query Wrapper (Node.js/Express)
|
|
683
|
+
|
|
684
|
+
```javascript
|
|
685
|
+
const pool = new Pool({ user: 'app_service', database: 'saas_app', max: 20 });
|
|
686
|
+
|
|
687
|
+
// Middleware: extract tenant from JWT (or subdomain, API key, etc.)
|
|
688
|
+
function tenantContext(req, res, next) {
|
|
689
|
+
const tenantId = req.auth?.tenantId;
|
|
690
|
+
if (!tenantId) return res.status(401).json({ error: 'Tenant not identified' });
|
|
691
|
+
req.tenantId = tenantId;
|
|
692
|
+
next();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// DB wrapper: sets tenant context via SET LOCAL (transaction-scoped)
|
|
696
|
+
async function tenantQuery(tenantId, queryText, params) {
|
|
697
|
+
const client = await pool.connect();
|
|
698
|
+
try {
|
|
699
|
+
await client.query('BEGIN');
|
|
700
|
+
await client.query("SELECT set_config('app.current_tenant', $1, true)", [tenantId]);
|
|
701
|
+
const result = await client.query(queryText, params);
|
|
702
|
+
await client.query('COMMIT');
|
|
703
|
+
return result;
|
|
704
|
+
} catch (err) {
|
|
705
|
+
await client.query('ROLLBACK');
|
|
706
|
+
throw err;
|
|
707
|
+
} finally {
|
|
708
|
+
client.release(); // Returns to pool WITHOUT tenant context
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Route handler — no WHERE tenant_id needed, RLS handles it
|
|
713
|
+
app.get('/api/orders', tenantContext, async (req, res) => {
|
|
714
|
+
const result = await tenantQuery(req.tenantId,
|
|
715
|
+
'SELECT order_id, customer, total, status FROM orders ORDER BY created_at DESC LIMIT 50');
|
|
716
|
+
res.json(result.rows);
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Tenant-Aware Integration Test
|
|
721
|
+
|
|
722
|
+
```javascript
|
|
723
|
+
describe('Multi-tenant data isolation', () => {
|
|
724
|
+
const tenantA = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
725
|
+
const tenantB = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
726
|
+
|
|
727
|
+
beforeAll(async () => {
|
|
728
|
+
await adminQuery(`INSERT INTO tenants (tenant_id, name, slug)
|
|
729
|
+
VALUES ($1, 'Tenant A', 'a'), ($2, 'Tenant B', 'b')`, [tenantA, tenantB]);
|
|
730
|
+
await tenantQuery(tenantA, `INSERT INTO orders (tenant_id, customer, total)
|
|
731
|
+
VALUES ($1, 'Alice', 100)`, [tenantA]);
|
|
732
|
+
await tenantQuery(tenantB, `INSERT INTO orders (tenant_id, customer, total)
|
|
733
|
+
VALUES ($1, 'Bob', 200)`, [tenantB]);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test('Tenant A sees only own data', async () => {
|
|
737
|
+
const r = await tenantQuery(tenantA, 'SELECT * FROM orders');
|
|
738
|
+
expect(r.rows).toHaveLength(1);
|
|
739
|
+
expect(r.rows[0].customer).toBe('Alice');
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test('Cross-tenant insert is rejected by RLS', async () => {
|
|
743
|
+
await expect(tenantQuery(tenantA,
|
|
744
|
+
`INSERT INTO orders (tenant_id, customer, total) VALUES ($1, 'Mallory', 999)`,
|
|
745
|
+
[tenantB])).rejects.toThrow(/row-level security/);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test('No tenant context = no data (default-deny)', async () => {
|
|
749
|
+
const client = await pool.connect();
|
|
750
|
+
try {
|
|
751
|
+
const r = await client.query('SELECT * FROM orders');
|
|
752
|
+
expect(r.rows).toHaveLength(0);
|
|
753
|
+
} finally { client.release(); }
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## Key Takeaways
|
|
761
|
+
|
|
762
|
+
1. **Start with shared database + tenant_id + RLS.** It is the simplest, cheapest, and most operationally sustainable model. You can always add isolation later; you cannot easily remove it.
|
|
763
|
+
|
|
764
|
+
2. **Include tenant_id in every composite primary key and foreign key from day one.** This single discipline preserves all future options — sharding, migration to dedicated databases, Citus distribution.
|
|
765
|
+
|
|
766
|
+
3. **Use Row Level Security as your primary isolation mechanism**, not application-level WHERE clauses. RLS is enforced by the database engine and cannot be bypassed by application bugs (as long as the application connects as a non-owner role).
|
|
767
|
+
|
|
768
|
+
4. **Build the hybrid model as a product feature.** The ability to migrate a tenant from shared to dedicated infrastructure is a premium offering that enterprise customers will pay for.
|
|
769
|
+
|
|
770
|
+
5. **Cross-tenant data leakage is an existential threat.** Invest more in preventing it than in any other aspect of your multi-tenant architecture. Integration tests that verify isolation on every deployment are non-negotiable.
|
|
771
|
+
|
|
772
|
+
6. **Database-per-tenant is a last resort, not a default.** At more than a few hundred tenants, the operational cost (migrations, monitoring, backups, connection management) exceeds the benefit for all but the most demanding compliance environments.
|
|
773
|
+
|
|
774
|
+
7. **Noisy neighbor is a solvable problem without physical isolation.** Per-tenant rate limiting, query timeouts, connection limits, and resource quotas handle 95% of noisy neighbor scenarios at a fraction of the cost of dedicated databases.
|
|
775
|
+
|
|
776
|
+
---
|
|
777
|
+
|
|
778
|
+
## Cross-References
|
|
779
|
+
|
|
780
|
+
- **[Database Scaling](../data/database-scaling.md)** — Vertical vs horizontal scaling, read replicas, connection pooling strategies that underpin multi-tenant database architecture
|
|
781
|
+
- **[Data Consistency](../data/data-consistency.md)** — Consistency guarantees across tenant isolation boundaries, especially during tenant migration between isolation levels
|
|
782
|
+
- **[Horizontal vs Vertical Scaling](horizontal-vs-vertical.md)** — When to scale up a shared database vs shard across nodes, directly relevant to multi-tenant growth stages
|
|
783
|
+
- **[Feature Flags and Rollouts](../../design/feature-flags-and-rollouts.md)** — Tenant-scoped feature flags for gradual rollouts, A/B testing per tenant tier, and tenant-specific configuration
|
|
784
|
+
|
|
785
|
+
---
|
|
786
|
+
|
|
787
|
+
## Sources
|
|
788
|
+
|
|
789
|
+
- [Salesforce Platform Multitenant Architecture](https://architect.salesforce.com/fundamentals/platform-multitenant-architecture)
|
|
790
|
+
- [AWS: Multi-tenant data isolation with PostgreSQL Row Level Security](https://aws.amazon.com/blogs/database/multi-tenant-data-isolation-with-postgresql-row-level-security/)
|
|
791
|
+
- [Azure: Multitenant SaaS Database Tenancy Patterns](https://learn.microsoft.com/en-us/azure/azure-sql/database/saas-tenancy-app-design-patterns)
|
|
792
|
+
- [Citus: Sharding a Multi-Tenant App with Postgres](https://docs.citusdata.com/en/stable/articles/sharding_mt_app.html)
|
|
793
|
+
- [Crunchy Data: Row Level Security for Tenants in Postgres](https://www.crunchydata.com/blog/row-level-security-for-tenants-in-postgres)
|
|
794
|
+
- [The Nile Dev: Shipping multi-tenant SaaS using Postgres Row-Level Security](https://www.thenile.dev/blog/multi-tenant-rls)
|
|
795
|
+
- [Citus 12: Schema-based sharding for PostgreSQL](https://www.citusdata.com/blog/2023/07/18/citus-12-schema-based-sharding-for-postgres/)
|
|
796
|
+
- [AWS re:Invent 2024: SaaS meets cell-based architecture](https://reinvent.awsevents.com/content/dam/reinvent/2024/slides/sas/SAS315_SaaS-meets-cell-based-architecture-A-natural-multi-tenant-fit.pdf)
|
|
797
|
+
- [OWASP: Multi Tenant Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Multi_Tenant_Security_Cheat_Sheet.html)
|
|
798
|
+
- [Bytebase: Multi-Tenant Database Architecture Patterns Explained](https://www.bytebase.com/blog/multi-tenant-database-architecture-patterns-explained/)
|
|
799
|
+
- [WorkOS: The developer's guide to SaaS multi-tenant architecture](https://workos.com/blog/developers-guide-saas-multi-tenant-architecture)
|
|
800
|
+
- [Inngest: Fixing noisy neighbor problems in multi-tenant queueing systems](https://www.inngest.com/blog/fixing-multi-tenant-queueing-concurrency-problems)
|