numbering-service — Local Development Setup
Version: 1.0 Status: Draft Owner: Commerce Engineering Last Updated: 2026-04-21 Companion: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY · API_CONTRACTS
1. Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 20 LTS+ | Runtime |
| pnpm | 9+ | Package manager |
| Docker Desktop | 24+ | Postgres, Redis, NATS, MinIO containers |
| grpcurl | latest | gRPC testing |
| mkcert | latest | Local mTLS certs |
| Prisma CLI | bundled | Migrations |
| direnv | optional | .envrc loading |
2. Clone and Install
git clone git@github.com:ghasi-technologies/ghasi-sms-gateway.git
cd ghasi-sms-gateway/services/numbering-service
pnpm install
3. Start Infrastructure
The monorepo includes a shared docker-compose.yml at repo root. From the repo root:
docker compose up -d postgres redis nats minio
This starts:
| Service | Port | Credentials |
|---|---|---|
| PostgreSQL 15 | localhost:5432 | ghasi / ghasi_dev (db ghasi_dev) |
| Redis 7 | localhost:6379 | none |
| NATS JetStream 2.10 | localhost:4222 | none (dev mode) |
| MinIO (S3-compat) | localhost:9000 (UI 9001) | minio / minio_dev |
Enable Redis keyspace notifications (one-time):
docker exec -it ghasi-redis redis-cli CONFIG SET notify-keyspace-events Ex
4. Database Setup
cd services/numbering-service
pnpm prisma migrate dev --name init_numbering_schema
pnpm prisma db seed
Seed populates:
- 5 mobile_operators matching real Afghan MNOs:
Roshan(MCC 412, MNC 40, prefix+9370,+9371,+9372)Etisalat-AF(MCC 412, MNC 50, prefix+9378,+9379)MTN-AF(MCC 412, MNC 01 + 20, prefix+9376,+9377)AWCC(MCC 412, MNC 03, prefix+9370,+9371)Salaam(MCC 412, MNC 88, prefix+9374)
- 3 lease contracts, one per primary MNO with sample 1000-MSISDN block each.
- 3 tenant pools:
dev-tenant-a(50/1/3 quotas),dev-tenant-b(10/0/1, vanity disabled),dev-tenant-vanity(100/5/10, vanity enabled). - 5 vanity-eligible short codes:
4000,5000,6000,7000,8000. - 1 MNO public signing key (matching the dev signing key in
test/fixtures/mno-keys/). - Sample inventory: 100 AVAILABLE MSISDNs per operator, 20 short codes, 5 alpha placeholders.
5. Environment Variables
Create .env.local:
NODE_ENV=development
LOG_LEVEL=debug
REGION_ID=kbl
GRPC_PORT=50061
HTTP_PORT=3021
DATABASE_URL=postgresql://ghasi:ghasi_dev@localhost:5432/ghasi_dev?schema=numbering
REDIS_URL=redis://localhost:6379/7
NATS_URL=nats://localhost:4222
# Disable mTLS for local dev (gated by NODE_ENV check)
GRPC_TLS_ENABLED=false
# Override quarantine for fast tests
QUARANTINE_MSISDN_DAYS=1
QUARANTINE_SHORT_CODE_DAYS=1
QUARANTINE_VANITY_DAYS=2
QUARANTINE_ALPHA_DAYS=0
# Reservation TTLs (override for fast tests)
RESERVATION_RESERVE_TTL_SECS=30
RESERVATION_HOLD_TTL_SECS=120
# S3 (MinIO local)
S3_ENDPOINT=http://localhost:9000
S3_REGULATOR_BUCKET=ghasi-regulator-exports-kbl
S3_ACCESS_KEY=minio
S3_SECRET_KEY=minio_dev
For mTLS testing locally:
# Generate dev certs
mkcert -install
pnpm dev:certs:generate # outputs ./certs/{ca,server,client-orchestrator}.{crt,key}
# In .env.local:
GRPC_TLS_ENABLED=true
TLS_CERT_PATH=./certs/server.crt
TLS_KEY_PATH=./certs/server.key
TLS_CA_PATH=./certs/ca.crt
6. Run the Service
# Hot reload
pnpm start:dev
# Debug mode (Node inspector)
pnpm start:debug
# Production mode (closer-to-real)
pnpm build && pnpm start:prod
Health endpoints:
http://localhost:3021/health/livehttp://localhost:3021/health/readyhttp://localhost:3021/metrics
7. Test the gRPC Endpoints
ValidateLease
grpcurl -plaintext \
-proto src/proto/numbering.proto \
-d '{
"identifier": "+93701234567",
"type": "MSISDN",
"tenant_id": "00000000-0000-4000-8000-00000000000A"
}' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/ValidateLease
Expected for the seeded tenant lease: { "valid": true, "leaseId": "lease_...", "effectiveUntil": "..." }.
Reserve
grpcurl -plaintext \
-proto src/proto/numbering.proto \
-d '{
"identifier": "+93701234999",
"type": "MSISDN",
"tenant_id": "00000000-0000-4000-8000-00000000000A",
"kind": "RESERVE",
"idempotency_key": "dev-test-001"
}' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/Reserve
Assign
grpcurl -plaintext \
-proto src/proto/numbering.proto \
-d '{
"identifier": "+93701234999",
"type": "MSISDN",
"tenant_id": "00000000-0000-4000-8000-00000000000A",
"term": "P30D",
"auto_renew": true,
"idempotency_key": "dev-test-002"
}' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/Assign
Lookup
grpcurl -plaintext -proto src/proto/numbering.proto \
-d '{ "identifier": "+93701234999", "type": "MSISDN" }' \
localhost:50061 \
ghasi.sms.numbering.v1.NumberingService/Lookup
8. Test the REST API
A dev JWT can be generated with pnpm token:dev (uses pre-signed dev JWKS from auth-service).
DEV_JWT=$(pnpm token:dev --role platform.numbering.admin)
# Browse available pool (admin)
curl -H "Authorization: Bearer $DEV_JWT" \
"http://localhost:3021/v1/admin/numbering/numbers?type=MSISDN&state=AVAILABLE&limit=20"
# List MNO contracts
curl -H "Authorization: Bearer $DEV_JWT" \
http://localhost:3021/v1/admin/numbering/contracts
# Trigger an MNO CSV import (uses fixture)
curl -H "Authorization: Bearer $DEV_JWT" \
-F "operatorId=$ROSHAN_ID" \
-F "contractId=$CONTRACT_ID" \
-F "signature=@test/fixtures/lease-batch-1.sig" \
-F "csvFile=@test/fixtures/lease-batch-1.csv" \
http://localhost:3021/v1/admin/numbering/blocks/import
# Tenant portal: reserve
TENANT_JWT=$(pnpm token:dev --tenant-id 00000000-0000-4000-8000-00000000000A --role sms:numbering:write)
curl -X POST \
-H "Authorization: Bearer $TENANT_JWT" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "type": "MSISDN" }' \
http://localhost:3021/v1/portal/numbering/+93701234500/reserve
9. Running Tests
# Unit tests (fast, no Docker)
pnpm test
# Unit tests with coverage report
pnpm test:cov
# Integration tests (uses testcontainers)
pnpm test:integration
# Watch mode
pnpm test:watch
# E2E (requires full Docker stack)
pnpm test:e2e
# Load test (ghz; requires service running)
pnpm test:load
10. Helpful Dev Commands
# Trigger a manual reservation cleanup
pnpm worker:reservation-cleanup
# Trigger a manual quarantine sweep
pnpm worker:quarantine-sweep
# Trigger a manual lease renewal cycle
pnpm worker:lease-renewal
# Trigger a one-off regulator export
pnpm worker:regulator-export --period=2026-04
# Verify audit hash chain integrity
pnpm worker:audit-chain-verify
# Clear Redis caches (forces PG reads)
pnpm cache:clear
# Time-travel: advance "now" for quarantine tests
pnpm dev:time-skip --hours=24
# Generate gRPC TypeScript types from .proto
pnpm proto:generate
# Tail structured logs prettily
pnpm logs:tail | jq
# Seed an extra test tenant
pnpm seed:tenant --tenant-id=$(uuidgen) --plan=enterprise
11. Common Issues
| Issue | Fix |
|---|---|
Could not connect to Postgres | Verify docker compose ps postgres; check DATABASE_URL |
| gRPC call hangs | Verify GRPC_TLS_ENABLED=false in .env.local for plaintext grpcurl |
| Reservation does not auto-expire | Run redis-cli CONFIG GET notify-keyspace-events — must include Ex |
IsVerified unreachable on alpha-Assign | Run pnpm mock:sender-id-registry to start the local mock |
| MNO CSV signature rejected | Use test/fixtures/mno-keys/dev-private.pem to sign your CSV with pnpm dev:sign-csv |
| Tests fail "relation does not exist" | Run pnpm prisma migrate dev |
| RLS returns zero rows for tenant | Ensure app.current_tenant_id is set by your handler middleware |
| Audit hash-chain test fails | Re-seed: pnpm prisma migrate reset |
12. Mock Dependencies
For isolated dev without external services:
# Mock sender-id-registry (returns IsVerified=true for any alpha)
pnpm mock:sender-id-registry # listens on localhost:50071
# Mock auth-service (issues dev JWTs without Keycloak)
pnpm mock:auth # listens on localhost:8080
# Mock billing-service (always returns PreviewCharge=ok)
pnpm mock:billing # listens on localhost:50081
Pointer env vars:
SENDER_ID_REGISTRY_GRPC_URL=localhost:50071
AUTH_SERVICE_URL=http://localhost:8080
BILLING_SERVICE_GRPC_URL=localhost:50081
End of LOCAL_DEV_SETUP.md