Skip to content
M
MANTRA

Example Decision

See what an architecture decision looks like in MANTRA — a standard format that AI can understand and enforce automatically.

This is a real example of an architecture decision in MANTRA. Every decision follows a 68-column format that enables AI assistants to understand, search, check compliance, and enforce your team's rules automatically.

4×4 Taxonomy

INT — Intent & Direction
A01 Vision & Outcome
A02 Problem Statement
A03 Scope & Non-Goals
A04 Principles & Values
ARCH — Architecture & Boundaries
A05 Domain & Bounded Context
A06 Service & Module Boundary THIS
A07 Data Ownership & Sovereignty
A08 Integration & Contract Model
CTL — Control, Policy & Risk
A09 Policy & Rules
A10 Approval & Authority
A11 Security & Compliance
A12 Risk & Blast Radius
EVO — Execution & Evolution
A13 Decision Lifecycle
A14 Reversibility & Exit Strategy
A15 Environment & Promotion
A16 Anti-Drift & Consistency
ARCH-A06-APP-DEC-001 v2.0.0 Active ENFORCING DECISION
Statement

Use PostgreSQL with Row-Level Security (RLS) for multi-tenant data isolation across all backend services

Rationale

The backend team experienced 2 cross-tenant data leakage incidents in Q4 2025 due to missing tenant_id filters at the application level. RLS moves enforcement to the database level so leakage is technically impossible even if a developer forgets to add a filter. This reduces developer cognitive load and permanently eliminates an entire class of bugs.

Summary

All tenant tables must use RLS. Filter at DB level, not application. Eliminates cross-tenant data leakage risk.

Domain
ARCH — Architecture & Boundaries
Aspect
A06 — Service & Module Boundary
Scope
APPLICATION
Blast Radius
HIGH
Type
DECISION
Change Type
REPLACEMENT
Created by
Ahmad Rizki (Tech Lead)
Approved by
Budi Santoso (CTO)
Tags SECURITYDATABASEMULTI_TENANCYRLSPOSTGRESQL
Tech Stack PostgreSQL 16pgx v5Bun.sql
Applies to backend/**/*.tsdatabase/migrations/**/*.sql

Decision Drivers

Why this decision was needed

  • 2 cross-tenant data leakage incidents in Q4 2025
  • Application-level tenant_id filters frequently missed by developers
  • Security audit recommended defense-in-depth at database level
  • New team members joining Q1 2026 — need automatic guardrails

Positive Consequences

  • +Cross-tenant data leakage becomes technically impossible
  • +Developers don't need to remember tenant_id filter on every query
  • +Compliance audits easier — isolation provable at DB level
  • +New developer onboarding safer — can't accidentally leak data

Negative Consequences

  • Query performance reduced ~2-5% due to RLS policy evaluation overhead
  • Migrating existing tables requires planned downtime
  • Query debugging becomes more complex due to hidden RLS filters

Alternatives Considered

Application-level filtering REJECTED

Filter tenant_id in every query at application code level. Previously used, but proven prone to human error.

Separate database per tenant REJECTED

Each tenant gets their own database. Perfect isolation but operational cost too high for a startup.

Schema-per-tenant DEFERRED

Each tenant gets their own schema in one database. Better than separate DBs but still hard to maintain.

Affected Areas

Backend
All repository layersQuery builderMigration scripts
Database
All tables with tenant dataRLS policiesConnection pooling
DevOps
Migration pipelineDatabase backup strategy

Constraints

Rules that MUST be followed — violations are detected automatically

C-001 REQUIREMENT BLOCKING SCHEMA

Every table with tenant data MUST have a tenant_id NOT NULL column

Good example
CREATE TABLE orders (id uuid, tenant_id uuid NOT NULL, ...);
Bad example
CREATE TABLE orders (id uuid, ...);  -- tenant_id missing
C-002 REQUIREMENT BLOCKING SCHEMA

Every table with tenant_id MUST have an active RLS policy

Good example
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.tenant_id')::uuid);
Bad example
-- Table has tenant_id but no RLS policy enabled
C-003 PROHIBITION BLOCKING REGEX

Raw SQL queries are PROHIBITED without explicit tenant_id filter

Good example
SELECT * FROM orders WHERE tenant_id = $1 AND status = $2
Bad example
SELECT * FROM orders WHERE status = 'active'  -- no tenant_id filter
C-004 PROHIBITION WARNING REGEX

Cross-tenant JOINs are PROHIBITED in a single query

Good example
SELECT o.* FROM orders o WHERE o.tenant_id = $1
Bad example
SELECT a.*, b.* FROM tenant_a.orders a JOIN tenant_b.orders b ON ...

Invariants

Conditions that MUST always hold true at runtime

  • I1 No cross-tenant data leakage is possible through any database query
  • I2 tenant_id column cannot be NULL on tables with RLS policies
  • I3 RLS policies must be active in all environments (development, staging, production)

Anti-Patterns

Patterns that are explicitly FORBIDDEN

  • X1 Disabling RLS to “speed up” queries in production
  • X2 Using superuser connections that bypass RLS for API endpoints
  • X3 Hardcoding tenant_id in application code instead of reading from session context

Enforcement Results

Automated check results against the codebase based on this decision's constraints.

PASS
src/database/migrations/042_users.sql
C-001, C-002

tenant_id column present (NOT NULL), RLS policy active

WARNING
src/api/services/user-service.ts
C-003

Line 47: SELECT query without explicit tenant_id filter

PASS
src/api/routes/admin.ts
C-001, C-003

All queries use parameterized tenant_id

FAIL
src/database/migrations/051_analytics.sql
C-002

Table analytics_events: RLS policy not enabled

2 PASS, 1 WARNING, 1 FAIL — 1 critical violation needs immediate fix.

Version History

Every change is recorded in append-only fashion — nothing can be silently deleted or modified.

1.0.0 INITIAL 2026-01-15 — Ahmad Rizki

Initial decision: use PostgreSQL RLS for multi-tenant isolation

1.1.0 EXTENSION 2026-02-20 — Ahmad Rizki

Add constraint C-004: prohibit cross-tenant JOINs

2.0.0 REPLACEMENT 2026-03-10 — Dewi Putri

Supersede v1.1.0: add NOT NULL invariant, anti-patterns, CI/CD enforcement, adoption status ENFORCING

Related Decisions

This decision is connected to other decisions in the dependency graph.

ARCH-A08-APP-DEC-003 DEPENDS_ON STRONG

JWT authentication must include tenant_id claim

RLS policy reads tenant_id from session variable set by JWT claim

CTL-A09-APP-DEC-001 RELATED_TO NORMAL

API rate limiting 1000 req/min per tenant

Rate limiting also uses tenant_id for quota isolation

CTL-A11-ORG-LAW-001 RELATED_TO NORMAL

Audit logging required for all data mutations

Audit logs must record tenant_id for traceability

Create your first decision

Free for 3 members and 25 decisions.