Local Dev Setup
:::info Source
Sourced from services/tenant-service/LOCAL_DEV_SETUP.md in the documentation repo.
:::
1. Prerequisites
| Tool | Version |
|---|---|
| Node.js | 20 LTS |
| pnpm | 9.x |
| Docker | 24.x |
2. Quick Start
cd services/tenant-service
cp .env.example .env.local
pnpm install
docker compose -f docker-compose.dev.yml up -d # postgres, redis, nats
pnpm db:migrate
pnpm seed
pnpm dev
Service listens on http://localhost:3002.
3. Required Dependencies
| Service | Port | Purpose |
|---|---|---|
| postgres | 5432 | Schema tenant; has ltree extension |
| redis | 6379 | Policy bundle cache, authz decision cache |
| nats | 4222 | Events |
| identity-service (stub) | 3001 | Seed user lookup |
4. Environment Variables (.env.example)
PORT=3002
LOG_LEVEL=debug
ENV=dev
DATABASE_URL=postgres://tenant:tenant@localhost:5432/tenant
DATABASE_POOL_MAX=20
DATABASE_RLS_ENFORCED=true
REDIS_URL=redis://localhost:6379/2
NATS_URL=nats://localhost:4222
NATS_STREAM=TENANT
POLICY_BUNDLE_SIGN_KMS_KEY_ID=alias/ghasi-tenant-policy-signer
POLICY_BUNDLE_REFRESH_SECONDS=60
DYNAMIC_GROUP_REEVAL_INTERVAL_MS=900000 # 15 min
FEATURE_FLAG_SERVICE_URL=http://localhost:3100
5. Seed Data
pnpm seed creates:
| Tenant | Slug | Type | Region | Plan |
|---|---|---|---|---|
| ACME Corp | acme | org | us | business |
| Globex | globex | org | eu | team |
| SoloAuthor | solo | provider | us | marketplace-provider |
Plus org units (Engineering, Sales), roles (from system defaults + custom CustomerSuccess), memberships (linking seeded users from identity-service).
6. Useful Commands
pnpm dev
pnpm test
pnpm test:integration # Testcontainers
pnpm policy:lint # Lint all ABAC predicates for tenant scope
pnpm policy:bundle # Build + sign policy bundle
pnpm seed
pnpm db:migrate
pnpm authz:check # CLI: POST /authz/check with seeded creds
7. Two-Tenant Isolation Test
pnpm test:isolation
Asserts:
- User in
acmecannot readglobexorg units / roles / memberships. - System role (
tenantId: null) never grants access without ABACctx.tenant_idmatch. - Dynamic group query
{member_of: "globex"}insideacmereturns empty.
8. Debugging
- Policy debugger:
POST /api/v1/authz/explainreturns decision tree with every predicate evaluated. - Dynamic group simulator:
POST /api/v1/tenants/{tid}/dynamic-groups/{id}/simulatereturns current members without persisting.