sms-firewall-service — Local Dev Setup
Version: 1.0 Status: Draft Owner: Trust and Safety + DevEx Last Updated: 2026-04-21 References: SERVICE_OVERVIEW.md, DEPLOYMENT_TOPOLOGY.md
Local development recipe for sms-firewall-service. Uses Docker Compose to stand up the service plus all infrastructure dependencies and mocks for external systems.
1. Prerequisites
| Tool | Version | Notes |
|---|---|---|
| Node.js | 20 LTS | Use nvm |
| pnpm | 9.x | corepack enable |
| Docker Engine | 24+ | With Compose v2 |
grpcurl | Latest | For gRPC testing |
psql | 16 | Postgres client |
redis-cli | 7 | Redis client |
nats CLI | 0.1.5+ | NATS testing |
jq | latest | JSON processing |
2. Repository Layout
Ghasi-SMS-Gateway/
services/sms-firewall-service/ ← this service spec
(application repo: Ghasi-edTech equivalent)/services/sms-firewall-service/ ← code
infra/docker/docker-compose.sms-firewall.yml
3. Docker Compose Recipe
Create infra/docker/docker-compose.sms-firewall.yml:
version: '3.9'
services:
postgres-firewall:
image: postgres:16-alpine
environment:
POSTGRES_DB: firewall
POSTGRES_USER: firewall
POSTGRES_PASSWORD: firewall
ports: ["5444:5432"]
volumes:
- firewall_pg_data:/var/lib/postgresql/data
- ./firewall-schema.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
- ./firewall-seed.sql:/docker-entrypoint-initdb.d/002-seed.sql:ro
redis-firewall:
image: redis:7-alpine
ports: ["6394:6379"]
command: redis-server --appendonly yes
nats:
image: nats:2.10-alpine
command: -js -m 8222
ports: ["4222:4222", "8222:8222"]
mock-fraud-feed:
image: ghasi/mock-fraud-feed:dev
ports: ["7100:7100"]
environment:
FEED_EMIT_INTERVAL_MS: 5000
FEED_AIT_PROB: 0.1
FEED_SIMBOX_PROB: 0.05
mock-mno-blocklist:
image: ghasi/mock-mno-blocklist:dev
ports: ["7200:7200"]
volumes:
- ./sample-national-blocklist.csv:/data/blocklist.csv:ro
mock-consent-ledger:
image: ghasi/mock-grpc-consent:dev
ports: ["50071:50071"]
mock-sender-id-registry:
image: ghasi/mock-grpc-senderid:dev
ports: ["50091:50091"]
sms-firewall:
image: ghasi/sms-firewall-service:dev
depends_on: [postgres-firewall, redis-firewall, nats, mock-fraud-feed, mock-mno-blocklist, mock-consent-ledger, mock-sender-id-registry]
ports: ["3051:3051", "50051:50051"]
environment:
NODE_ENV: development
LOG_LEVEL: debug
DATABASE_URL: postgres://firewall:firewall@postgres-firewall:5432/firewall
REDIS_URL: redis://redis-firewall:6379/0
NATS_URL: nats://nats:4222
GRPC_PORT: 50051
HTTP_PORT: 3051
GRPC_TLS_ENABLED: "false"
FRAUD_FEED_URL: http://mock-fraud-feed:7100
MNO_BLOCKLIST_URL: http://mock-mno-blocklist:7200
CONSENT_LEDGER_GRPC: mock-consent-ledger:50071
SENDER_ID_REGISTRY_GRPC: mock-sender-id-registry:50091
# Feature flags (local defaults)
ML_MODEL_ENABLED: "false" # Rule-based only in dev
FEDERATION_ENABLED: "true"
FIREWALL_EMERGENCY_BYPASS: "false"
volumes:
firewall_pg_data:
Start:
docker compose -f infra/docker/docker-compose.sms-firewall.yml up -d
Verify health:
curl -s http://localhost:3051/health/ready | jq
grpcurl -plaintext localhost:50051 list
4. Seed Data
firewall-seed.sql (in infra/docker/):
-- Sample firewall rules
INSERT INTO firewall.rules (id, name, direction, action, predicate_json, severity, enabled) VALUES
('rule_ait_base', 'AIT high-confidence', 'TRANSIT', 'BLOCK', '{"type":"ml","model":"ait-v1","threshold":0.92}', 'HIGH', true),
('rule_simbox_base', 'SIM-box pattern', 'INBOUND', 'QUARANTINE', '{"type":"ml","model":"simbox-v1","threshold":0.85}', 'HIGH', true),
('rule_consent_block', 'No consent → block', 'TRANSIT', 'BLOCK', '{"type":"consent","require":"granted"}', 'CRITICAL', true),
('rule_senderid_ver', 'Unverified sender on P0/P1', 'TRANSIT', 'BLOCK', '{"type":"senderid","require":"NOTARISED"}', 'HIGH', true),
('rule_geo_af_only', 'Afghanistan-only inbound', 'INBOUND', 'BLOCK', '{"type":"geo","allow":["AF"]}', 'MEDIUM', true);
-- Sample blocklist entries
INSERT INTO firewall.blocklists (id, name, type) VALUES
('bl_national_seed', 'National seed blocklist', 'MSISDN');
INSERT INTO firewall.blocklist_entries (blocklist_id, pattern, pattern_type, ttl_seconds, reason) VALUES
('bl_national_seed', '+93700000000', 'EXACT', NULL, 'Test reserved MSISDN'),
('bl_national_seed', '+93701111111', 'EXACT', NULL, 'Sample blocked subscriber');
-- SIM-box known patterns (for rule-based fallback)
INSERT INTO firewall.simbox_signals (key, pattern_json) VALUES
('simbox_high_volume_in', '{"inbound_per_msisdn_5min": {"gte": 500}}'),
('simbox_origin_cluster', '{"distinct_originator_cidr_ratio": {"lt": 0.1}}');
5. Common Commands
5.1 Verify gRPC shape
grpcurl -plaintext localhost:50051 list ghasi.firewall.v1.SmsFirewallService
5.2 Test EvaluateTransit (MT outbound filter)
grpcurl -plaintext -d '{
"messageId": "msg_test_001",
"tenantId": "tnt_demo",
"senderId": "TESTBRAND",
"recipientMsisdn": "+93701234567",
"lane": "P3",
"contentFingerprint": "abc123..."
}' localhost:50051 ghasi.firewall.v1.SmsFirewallService/EvaluateTransit
Expected:
{
"verdict": "ALLOW",
"reason": "",
"cachedAt": "2026-04-21T08:00:00Z",
"evidence": []
}
5.3 Test FilterInbound (MO inbound filter)
grpcurl -plaintext -d '{
"originatorMsisdn": "+93701234567",
"destinationShortcode": "12345",
"content": "Hello from subscriber",
"receivedAt": "2026-04-21T08:00:00Z"
}' localhost:50051 ghasi.firewall.v1.SmsFirewallService/FilterInbound
5.4 Trigger a blocklist-match test
grpcurl -plaintext -d '{
"messageId":"msg_blocked_001",
"tenantId":"tnt_demo",
"senderId":"TESTBRAND",
"recipientMsisdn":"+93700000000",
"lane":"P3",
"contentFingerprint":"x"
}' localhost:50051 ghasi.firewall.v1.SmsFirewallService/EvaluateTransit
Expected: verdict: BLOCK, reason: "BLOCKLIST_MATCH".
5.5 Simulate a fraud signal arrival
nats pub fraud.detected.ait.v1 '{"msisdn":"+93701234567","confidence":0.95,"detectedAt":"2026-04-21T08:00:00Z","campaignId":"camp_001"}'
Re-run EvaluateTransit for the same MSISDN → expect verdict to flip to BLOCK.
5.6 Inspect the audit chain
psql postgres://firewall:firewall@localhost:5444/firewall -c "
SELECT id, decision_at, action, rule_id, encode(record_hash, 'hex') as record_hash
FROM firewall.audit
ORDER BY decision_at DESC
LIMIT 10;
"
5.7 Verify chain integrity locally
pnpm --filter @ghasi/sms-firewall-service audit:verify -- --from 2026-04-21 --to 2026-04-22
5.8 Run tests
# Unit
pnpm --filter @ghasi/sms-firewall-service test
# Integration (requires docker-compose up)
pnpm --filter @ghasi/sms-firewall-service test:integration
# Contract
pnpm --filter @ghasi/sms-firewall-service test:contract
# Load (lightweight local benchmark)
pnpm --filter @ghasi/sms-firewall-service test:load:local
5.9 Reset state (for deterministic tests)
docker compose -f infra/docker/docker-compose.sms-firewall.yml down -v
docker compose -f infra/docker/docker-compose.sms-firewall.yml up -d
6. Environment Variables Reference
| Variable | Default (dev) | Purpose |
|---|---|---|
NODE_ENV | development | Standard Node env |
LOG_LEVEL | debug | Pino log level |
DATABASE_URL | postgres://... | Firewall schema |
REDIS_URL | redis://... | Hot-cache |
NATS_URL | nats://nats:4222 | Event bus |
GRPC_PORT | 50051 | gRPC listener |
HTTP_PORT | 3051 | HTTP admin / health |
GRPC_TLS_ENABLED | false | mTLS off in dev |
FRAUD_FEED_URL | mock | Fraud-intel signal source |
MNO_BLOCKLIST_URL | mock | National-blocklist federation source |
CONSENT_LEDGER_GRPC | mock | Consent gRPC target |
SENDER_ID_REGISTRY_GRPC | mock | Sender-ID gRPC target |
ML_MODEL_ENABLED | false | Disable ML in dev; rule-based only |
FEDERATION_ENABLED | true | Federation sync active |
FIREWALL_EMERGENCY_BYPASS | false | Emergency-bypass flag (prod only) |
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
/health/ready returns 503 | Postgres / Redis / NATS not up | docker compose ps, restart deps |
gRPC returns UNAVAILABLE | Server not listening on :50051 | Check service logs; verify GRPC_PORT |
| All verdicts say ALLOW | Rules not seeded or disabled | Re-run firewall-seed.sql; check firewall.rules.enabled |
| Fraud signals not arriving | mock-fraud-feed not running or NATS subject mismatch | docker compose logs mock-fraud-feed; nats stream ls |
| Audit chain verification fails | Seed file bug or manual UPDATE on firewall.audit | Reset volume (§5.9) and re-seed |
| High latency in dev | Redis + Postgres container warm-up | Give 30 s after up -d before load |
8. Running with Kubernetes (kind) Instead of Compose
For those preferring kind:
kind create cluster --name firewall-dev
helm dependency build infra/helm/sms-firewall-service
helm install firewall infra/helm/sms-firewall-service -f infra/helm/sms-firewall-service/values.dev.yaml
See infra/helm/sms-firewall-service/ (forthcoming) for full chart.