sender-id-registry-service — Local Development Setup
Version: 1.0 Status: Draft Owner: Trust & Safety + Platform Engineering Last Updated: 2026-04-21 Companion: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY
1. Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 20 LTS+ | Runtime |
| pnpm | 9+ | Package manager (monorepo workspace) |
| Docker Desktop | 24+ | Postgres, Redis, NATS, MinIO, mocks |
| grpcurl | latest | gRPC testing |
| mkcert | latest | Local mTLS certs |
| dnsmasq (optional) | latest | Local DNS test server |
2. Clone and Install
git clone git@github.com:ghasi/sms-gateway.git
cd sms-gateway/services/sender-id-registry-service
pnpm install
3. Start Infrastructure
The monorepo's shared docker-compose includes a sender-id-registry profile:
# From repo root
docker compose --profile sid up -d
This starts:
- PostgreSQL on
localhost:5432(user:ghasi, password:ghasi_dev, schema:sender_id_registry) - Redis on
localhost:6379(DB 5 used by this service) - NATS JetStream on
localhost:4222 - MinIO (S3-compatible) on
localhost:9000with bucketsghasi-sid-kyc-localandghasi-sid-regulator-export-local - SoftHSM2 sidecar on
localhost:5657(for regulator-export signing in dev) - mock-dns-server on
localhost:8053(returns canned TXT records for DNS-TXT verification testing) - mock-regulator-sftp on
localhost:2222(logs received files to./tmp/atra-mock/) - mock-channel-router on
localhost:50061(records OTP requests to./tmp/otp-log.jsonl)
Compose snippet (services/sender-id-registry-service/docker-compose.yml)
services:
sid-postgres:
image: postgres:15
environment:
POSTGRES_USER: ghasi
POSTGRES_PASSWORD: ghasi_dev
POSTGRES_DB: ghasi_dev
ports: ["5432:5432"]
sid-redis:
image: redis:7-alpine
ports: ["6379:6379"]
sid-nats:
image: nats:2.10
command: -js -m 8222
ports: ["4222:4222", "8222:8222"]
sid-minio:
image: minio/minio:latest
environment:
MINIO_ROOT_USER: ghasidev
MINIO_ROOT_PASSWORD: ghasidevsecret
command: server /data --console-address ":9001"
ports: ["9000:9000", "9001:9001"]
volumes: [./tmp/minio-data:/data]
sid-softhsm:
image: softhsm/softhsm2:latest
environment:
SOFTHSM2_PIN: 1234
SOFTHSM2_SOPIN: 5678
ports: ["5657:5657"]
sid-mock-dns:
image: ghcr.io/ghasi/mock-dns-server:latest
ports: ["8053:53/udp", "8053:53/tcp"]
volumes: [./test/mock-dns/zones.yaml:/etc/zones.yaml]
sid-mock-regulator-sftp:
image: atmoz/sftp:latest
command: regulator:regulator:1001
ports: ["2222:22"]
volumes: [./tmp/atra-mock:/home/regulator/incoming]
sid-mock-channel-router:
image: ghcr.io/ghasi/mock-channel-router:latest
ports: ["50061:50061"]
volumes: [./tmp/otp-log.jsonl:/var/log/otp.jsonl]
4. Database Setup
# From services/sender-id-registry-service
pnpm db:migrate # apply migrations to create sender_id_registry schema
pnpm db:seed # seed restricted patterns + sample sender-IDs
The seed script populates:
- All default
restricted_patterns(BANK*, GOV*, MNO*, JUDICIAL, HEALTH, EMERGENCY) - 3 sample sender-IDs in different states:
sid_dev_active_bank:BANK-DEVALPHA, ACTIVE, NOTARISED + DOMAIN_DNSsid_dev_pending:RETAIL-DEVALPHA, KYC_REVIEWsid_dev_suspended:LOWREP-DEVALPHA, SUSPENDED (low reputation)
- 6 sample KYC documents in MinIO bucket
- 1 sample regulator export
5. Environment Variables
Create .env.local in services/sender-id-registry-service/:
NODE_ENV=development
LOG_LEVEL=debug
GRPC_PORT=50091
HTTP_PORT=3091
REGION=local
DATABASE_URL=postgresql://ghasi:ghasi_dev@localhost:5432/ghasi_dev?schema=sender_id_registry
REDIS_URL=redis://localhost:6379/5
NATS_URL=nats://localhost:4222
NATS_CREDS_PATH=./test/nats.creds.dev
S3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY=ghasidev
S3_SECRET_KEY=ghasidevsecret
S3_KYC_BUCKET=ghasi-sid-kyc-local
S3_REGULATOR_EXPORT_BUCKET=ghasi-sid-regulator-export-local
# Disable mTLS in local dev
GRPC_TLS_ENABLED=false
# SoftHSM2 for regulator-export signing
HSM_PKCS11_LIB=/usr/lib/softhsm/libsofthsm2.so
HSM_REGULATOR_EXPORT_KEY_SLOT=0
HSM_PIN=1234
# Vault disabled in dev — use file-based keys
VAULT_ADDR=disabled
KYC_KEK_DEV_KEY=./test/keys/dev-kek.key
# Mock external services
ATRA_SFTP_HOST=localhost
ATRA_SFTP_PORT=2222
ATRA_SFTP_USER=regulator
ATRA_SFTP_KEY=./test/keys/regulator.key
DNS_RESOLVER_PRIMARY=127.0.0.1:8053
DNS_RESOLVER_SECONDARY=127.0.0.1:8053
# AI mock
AI_LLM_URL=mock
AI_OCR_URL=mock
# Channel router
CHANNEL_ROUTER_GRPC_URL=localhost:50061
OTP_TTL_SECONDS=60 # shorter for dev convenience
OTP_RATE_LIMIT_PER_HOUR=100
6. Run the Service
# Development with hot reload
pnpm start:dev
# Or run in debug mode
pnpm start:debug
Service logs appear in terminal. Health endpoints:
http://localhost:3091/health/livehttp://localhost:3091/health/readyhttp://localhost:3091/metrics
7. Test the gRPC Endpoint
grpcurl -plaintext \
-proto src/proto/sid.proto \
-d '{
"sender_id": "BANK-DEV",
"type": "ALPHA",
"tenant_id": "00000000-0000-4000-8000-000000000001",
"trace_id": "local-test-1"
}' \
localhost:50091 \
ghasi.sms.sid.v1.SenderIdRegistryService/Verify
Expected:
{
"status": "ACTIVE",
"currentLevel": "NOTARISED",
"hasDomainDns": true,
"lastVerifiedAt": "2025-11-04T10:00:00Z",
"reputationScore": 92,
"meetsRequiredLevel": true,
"registrantOrgName": "Bank Dev (Sample)"
}
Try a tenant-mismatch attempt:
grpcurl -plaintext -proto src/proto/sid.proto \
-d '{ "sender_id": "BANK-DEV", "type": "ALPHA", "tenant_id": "11111111-1111-1111-1111-111111111111" }' \
localhost:50091 ghasi.sms.sid.v1.SenderIdRegistryService/Verify
Expected: "status": "TENANT_MISMATCH".
8. Test the REST API
# Generate dev JWT
DEV_JWT=$(pnpm token:dev --tenant 00000000-0000-4000-8000-000000000001 --role tenant)
# Submit a sender-ID
curl -X POST -H "Authorization: Bearer $DEV_JWT" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d @test/fixtures/submit-retail.json \
http://localhost:3091/v1/sender-ids
# List own sender-IDs
curl -H "Authorization: Bearer $DEV_JWT" http://localhost:3091/v1/sender-ids
# Public search (no auth)
curl 'http://localhost:3091/v1/sender-ids/public/search?q=BANK-DEV'
# Admin: list reviewer queue
ADMIN_JWT=$(pnpm token:dev --role platform.sid.reviewer)
curl -H "Authorization: Bearer $ADMIN_JWT" \
'http://localhost:3091/v1/admin/sender-ids?state=KYC_REVIEW'
# Admin: approve a registration
curl -X POST -H "Authorization: Bearer $ADMIN_JWT" -H "Content-Type: application/json" \
-d '{"action":"APPROVE","reason":"All KYC verified"}' \
http://localhost:3091/v1/admin/sender-ids/sid_dev_pending/decision
9. Running Tests
# Unit tests (fast, no infra)
pnpm test
# Unit tests with coverage
pnpm test:cov
# Integration tests (requires Docker)
pnpm test:integration
# Contract tests
pnpm test:contract
# Watch mode
pnpm test:watch
# E2E tests
pnpm test:e2e
# Load test (requires ghz)
pnpm test:load:verify
10. Database Migrations
pnpm db:migrate:dev --name add_some_column # create migration
pnpm db:migrate # apply pending
pnpm db:reset # destructive: drop + reseed
pnpm db:partition:provision # provision next 3 monthly partitions
11. Helpful Dev Commands
# Trigger reputation cron manually
pnpm cli reputation:run
# Trigger regulator export manually
pnpm cli export:run --triggered-by ON_DEMAND
# Reset Redis caches
pnpm cli cache:clear
# Tail structured logs
pnpm logs:tail | jq
# Generate gRPC proto TypeScript types
pnpm proto:generate
# Add a restricted-pattern interactively
pnpm cli pattern:add --pattern '^FAKE.*$' --category OTHER_RESERVED --notes "Local test pattern"
# Inspect outbox lag
pnpm cli outbox:status
# Verify KYC blob hash (tamper check)
pnpm cli kyc:verify-hash --kyc-doc-id kyc_dev_001
12. Common Issues
| Issue | Fix |
|---|---|
Could not connect to Postgres | Ensure docker compose --profile sid up -d is running; check DATABASE_URL |
| gRPC call hangs | Check GRPC_TLS_ENABLED=false in .env.local |
| DNS verification always fails | Ensure mock-dns-server is running on port 8053 and contains the expected zone |
| OTP test cannot verify | Check ./tmp/otp-log.jsonl for the issued OTP plaintext (mock channel-router writes here) |
| MinIO upload fails | Run mc alias set local http://localhost:9000 ghasidev ghasidevsecret then mc mb local/ghasi-sid-kyc-local |
| HSM signing fails | Reinitialise SoftHSM token: pnpm hsm:init |
| Restricted-pattern not enforced after change | In-process cache TTL 5 min; run pnpm cli cache:clear or wait |
| Tests fail with "relation does not exist" | Run pnpm db:migrate |
13. Useful URLs
- MinIO console:
http://localhost:9001(ghasidev / ghasidevsecret) - NATS monitoring:
http://localhost:8222 - Postgres CLI:
psql postgresql://ghasi:ghasi_dev@localhost:5432/ghasi_dev - Service metrics:
http://localhost:3091/metrics - OpenAPI:
http://localhost:3091/v1/sid/openapi.json