Skip to main content

Number Intelligence Service — Local Dev Setup

Version: 1.0 Status: Draft Owner: Messaging Core Last Updated: 2026-04-21 Companion: TESTING_STRATEGY · DEPLOYMENT_TOPOLOGY

1. Prerequisites

ToolVersionPurpose
Node.js20 LTS+Runtime
pnpm9+Package manager
Docker Desktop24+Postgres, Redis, NATS, mock-MNO-SFTP, mock-SS7-gateway, MinIO, Vault dev
grpcurllatestgRPC ad-hoc testing
psql16+Direct DB access
nats CLI0.1+Stream/subject inspection
mkcertlatestLocal mTLS dev certs (optional)
jq1.7+Pretty-print JSON logs
Python3.11+Audit-chain verifier (parallel impl)

2. Clone and install

git clone git@github.com:ghasi/sms-gateway.git
cd sms-gateway/services/number-intelligence-service
pnpm install

3. Start infrastructure

The repo provides a service-scoped docker-compose.dev.yml at services/number-intelligence-service/docker-compose.dev.yml:

docker compose -f docker-compose.dev.yml up -d

This starts:

ContainerHost:PortNotes
PostgreSQL 16localhost:5432user ghasi, password ghasi_dev, db ghasi_dev; schema numint
Redis 7 (single-node)localhost:6379DB 5 used by service
NATS 2.10 (JetStream)localhost:4222Streams pre-created (NUMBER_INTELLIGENCE_EVENTS, NUMINT_RECONCILIATION, NUMINT_EIR, NUMINT_AUDIT_OPS, NUMINT_BILLING)
Mock MNO SFTPlocalhost:2222 (SFTP)Serves CSVs from test/fixtures/mno-mnp/ for afghan-wireless, mtn-afghanistan, etisalat-af, roshan
Mock ATRA CEIR SFTPlocalhost:2223 (SFTP)Serves blacklist CSV from test/fixtures/atra-ceir/
Mock SS7 gatewaylocalhost:50074 (gRPC)Implements LiveLookup proto; deterministic responses driven by test/fixtures/hlr-fixtures.json
MinIOlocalhost:9000 (S3)Buckets numint-mnp-raw, numint-eir-raw, numint-hlr-pcap, numint-audit-cold pre-created
Vault dev modelocalhost:8200Token dev-only-root-token; pre-seeded paths

The compose file mounts test/fixtures/ into the relevant containers; swap fixtures during testing.


4. Database setup

pnpm prisma migrate dev --name init_numint_schema
pnpm db:seed

The seed script (scripts/seed.ts) populates:

  • MNO snapshots for the four major Afghan MNOs (afghan-wireless, mtn-afghanistan, etisalat-af, roshan, salaam) with prefix tables matching ATRA's published numbering plan.
  • Sample number_records — 1 000 MSISDNs in the reserved +9379999XXXX test range, mixed across MNOs and MNP states.
  • Sample portability_history — 50 historical port events.
  • Sample eir_records — 100 BLACKLIST + 50 GREYLIST IMEIs (synthetic Luhn-valid).
  • Sample tenant_lookup_quotast_dev_acme with default plan (10 RPS, 100 000/month).
  • Mock platform pepper + tenant salt in Vault dev.

5. Environment variables

Create .env.local in services/number-intelligence-service/:

NODE_ENV=development
LOG_LEVEL=debug
GRPC_PORT=50073
HTTP_PORT=3073

DATABASE_URL=postgresql://ghasi:ghasi_dev@localhost:5432/ghasi_dev?schema=numint
DATABASE_REPLICA_URL=postgresql://ghasi:ghasi_dev@localhost:5432/ghasi_dev?schema=numint
REDIS_URL=redis://localhost:6379/5
NATS_URL=nats://localhost:4222

# Disable mTLS for local dev
GRPC_TLS_ENABLED=false

# Vault dev mode
VAULT_ADDR=http://localhost:8200
VAULT_TOKEN=dev-only-root-token

# Pepper (loaded from Vault in non-dev)
MSISDN_PEPPER=dev-pepper-do-not-use-in-prod
IMEI_PEPPER=dev-imei-pepper

# Mock SS7 gateway
HLR_GATEWAY_ENDPOINT=localhost:50074

# Mock MNO SFTP base
MNO_SFTP_BASE=sftp://test:test@localhost:2222
ATRA_CEIR_SFTP=sftp://test:test@localhost:2223/ceir/blacklist.csv

# MinIO
S3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY=minio
S3_SECRET_KEY=minio_dev_pwd
S3_BUCKET_MNP_RAW=numint-mnp-raw
S3_BUCKET_EIR_RAW=numint-eir-raw
S3_BUCKET_HLR_PCAP=numint-hlr-pcap

# Cache
CACHE_WARM_TARGET=10000
LRU_MAX_ENTRIES=10000

# Per-MNO TPS (dev defaults; production loads from operator-management)
TPS_AFGHAN_WIRELESS=100
TPS_MTN=100
TPS_ETISALAT=100
TPS_ROSHAN=100

# Region (for metric labels)
REGION=af-kabul-dev

To enable mTLS locally:

pnpm gen:dev-certs # uses mkcert; output to ./certs/
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-path API (gRPC + REST)
pnpm dev

# Batch jobs (in another terminal — disabled by default in dev)
pnpm dev:batch

# Audit verifier (Python; in a third terminal)
cd services/number-intelligence-service/python-verifier
poetry install && poetry run python verifier.py --once

The hot-path service exposes:

  • gRPC at localhost:50073
  • REST at localhost:3073 (Swagger UI at /v1/numint/openapi.json)
  • /metrics, /health/ready, /health/live

7. Common dev commands

7.1 Lookup an MSISDN via gRPC

grpcurl -plaintext -d '{"e164":"+93701234567"}' \
localhost:50073 ghasi.sms.numint.v1.NumberIntelligenceService/ResolveMsisdn

7.2 Lookup via REST (Public Lookup API)

# Mint a dev tenant JWT
TOKEN=$(pnpm dev:mint-jwt --tenant t_dev_acme --scope numint:lookup)

curl -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: t_dev_acme" \
"http://localhost:3073/v1/lookup/+93701234567?maxStaleness=86400" | jq

7.3 Trigger MNP reconciliation on demand

curl -H "Authorization: Bearer $ADMIN_TOKEN" -X POST \
-d '{"mnoId":"mtn-afghanistan","fileUrl":"sftp://test:test@localhost:2222/mnp/2026-04-20.csv"}' \
http://localhost:3073/v1/admin/numint/mnp/runs

7.4 Force a live HLR probe

grpcurl -plaintext -d '{"e164":"+93701234567","tps_wait_ms":500}' \
localhost:50073 ghasi.sms.numint.v1.NumberIntelligenceService/ProbeHlr

7.5 Test tenant quota enforcement

# Hit the API rapidly; expect 429 after the configured RPS
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: t_dev_acme" "http://localhost:3073/v1/lookup/+93701234567"
done

7.6 Inspect outbox / NATS

psql $DATABASE_URL -c "SELECT subject, count(*) FROM numint.outbox WHERE published_at IS NULL GROUP BY subject"

nats stream report
nats consumer info NUMBER_INTELLIGENCE_EVENTS

7.7 Verify audit chain manually

pnpm chain:verify --table lookup_audit --partition lookup_audit_2026_04
pnpm chain:verify --table portability_history --mno mtn-afghanistan

7.8 Trigger MNP conflict for testing

The seed includes a fixture that lists +93701555000 as ported to both afghan-wireless and mtn-afghanistan on consecutive days.

# Run both MNOs' reconciliations
curl -X POST .../v1/admin/numint/mnp/runs -d '{"mnoId":"afghan-wireless","fileUrl":"sftp://.../afghan-wireless-conflict.csv"}'
curl -X POST .../v1/admin/numint/mnp/runs -d '{"mnoId":"mtn-afghanistan","fileUrl":"sftp://.../mtn-conflict.csv"}'

# Inspect the conflict
curl http://localhost:3073/v1/admin/numint/mnp/conflicts | jq

# Resolve
curl -X POST .../v1/admin/numint/mnp/conflicts/cfl_xxx/resolve \
-d '{"resolution":"B_WINS","note":"MTN file dated later"}'

8. Running tests

# Unit
pnpm test

# Integration (Testcontainers — slower)
pnpm test:integration

# Contract (Pact)
pnpm test:contract

# Load test (k6 must be installed separately)
pnpm test:load:hot-path

# Property-based (fast-check)
pnpm test:prop

# Coverage
pnpm test:coverage

Coverage reports land in coverage/ and lcov.info. Per-area thresholds enforced in jest.config.ts.


9. Troubleshooting

SymptomLikely causeFix
pnpm dev fails with "PEPPER_VERSION mismatch"Vault dev seed not runpnpm vault:seed
LookupEir returns INVALID_ARGUMENT for valid IMEILuhn fixture wrongVerify with pnpm imei:gen
MNP recon job hangsMock SFTP container not runningdocker compose ps; restart
Prefix-table fallback returning UNKNOWN for AF MSISDNsATRA CSV not seeded into Vaultpnpm vault:seed:prefix-table
Live HLR probe returns ADAPTER_DOWNMock SS7 gateway crasheddocker compose logs mock-ss7; restart
Outbox not drainingNATS not connectedCheck NATS_URL; nats server check connection
chain:verify reports BROKENPepper rotated mid-run; or genuine bugConfirm pepper_version consistency; if genuine, file an issue

10. Tearing down

docker compose -f docker-compose.dev.yml down -v # -v wipes volumes

11. Tips for productive local dev

  • Watch logs with jq: pnpm dev | jq -c 'select(.event | startswith("numint.lookup"))' to see only lookup events.
  • Pin a single-pod scenario for cache testing: the LRU is in-process; multi-pod local runs would not exercise the same LRU instance. Use pnpm dev:single for deterministic LRU behaviour.
  • Hot-reload prefix table: curl -X POST http://localhost:3073/v1/admin/numint/internal/reload-prefix-table (dev-only endpoint).
  • Enable verbose SS7 PCAP capture: set HLR_PCAP_SAMPLE_RATE=1.0 in .env.local to capture every probe locally for debugging.