cdr-mediation-service — Local Dev Setup
Version: 1.0 Status: Draft Owner: Commerce + DevEx Last Updated: 2026-04-21 References: SERVICE_OVERVIEW.md, DEPLOYMENT_TOPOLOGY.md, TESTING_STRATEGY.md
1. Prerequisites
| Tool | Version |
|---|---|
| Node.js | 20 LTS |
| pnpm | 9.x |
| Docker Engine | 24+ |
psql | 16 |
nats CLI | 0.1.5+ |
mc (MinIO client) | latest |
ssh, sftp | — |
pkcs11-tool | latest (optional, for softhsm inspection) |
jq | latest |
2. Docker Compose
infra/docker/docker-compose.cdr-mediation.yml:
version: '3.9'
services:
postgres-cdr:
image: postgres:16-alpine
environment:
POSTGRES_DB: cdr
POSTGRES_USER: cdr
POSTGRES_PASSWORD: cdr
ports: ["5447:5432"]
volumes:
- cdr_pg_data:/var/lib/postgresql/data
- ./cdr-mediation/seed/schema.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
- ./cdr-mediation/seed/seed.sql:/docker-entrypoint-initdb.d/002-seed.sql:ro
redis-cdr:
image: redis:7-alpine
ports: ["6397:6379"]
nats:
image: nats:2.10-alpine
command: -js -m 8222
ports: ["4225:4222"]
minio-cdr:
image: minio/minio:latest
environment:
MINIO_ROOT_USER: cdrminio
MINIO_ROOT_PASSWORD: cdrminio
command: server /data --console-address ":9001"
ports: ["9200:9000", "9201:9001"]
volumes: [cdr_minio_data:/data]
softhsm:
image: ghasi/softhsm-dev:2.6
volumes:
- cdr_hsm_data:/var/lib/softhsm
- ./cdr-mediation/hsm:/hsm-init:ro
clickhouse:
image: clickhouse/clickhouse-server:24.3-alpine
ports: ["8123:8123", "9009:9000"]
volumes:
- ./cdr-mediation/clickhouse-init:/docker-entrypoint-initdb.d:ro
mock-atra-sftp:
image: atmoz/sftp:latest
command: atra:atra:1001::upload
volumes:
- cdr_atra_sftp:/home/atra
ports: ["2222:22"]
mock-atra-https:
image: ghasi/mock-atra-https:dev
ports: ["8443:8443"]
environment:
ATRA_ACK_DELAY_MS: "500"
ATRA_REJECT_PROB: "0.0"
cdr-mediation:
image: ghasi/cdr-mediation-service:dev
depends_on: [postgres-cdr, redis-cdr, nats, minio-cdr, softhsm, clickhouse, mock-atra-sftp, mock-atra-https]
ports: ["3071:3071", "3072:3072", "3073:3073"]
environment:
NODE_ENV: development
LOG_LEVEL: debug
DATABASE_URL: postgres://cdr:cdr@postgres-cdr:5432/cdr
REDIS_URL: redis://redis-cdr:6379/0
NATS_URL: nats://nats:4222
S3_ENDPOINT: http://minio-cdr:9000
S3_ACCESS_KEY: cdrminio
S3_SECRET_KEY: cdrminio
S3_BUCKET_HOT: cdr-hot
S3_BUCKET_COLD: cdr-cold
CLICKHOUSE_URL: http://clickhouse:8123/cdr
HSM_PKCS11_LIB: /softhsm/libsofthsm2.so
HSM_TOKEN_LABEL: cdr-dev
HSM_PIN_FILE: /hsm-init/pin.txt
ATRA_SFTP_HOST: mock-atra-sftp
ATRA_SFTP_PORT: "22"
ATRA_SFTP_USER: atra
ATRA_SFTP_PASSWORD: atra
ATRA_SFTP_UPLOAD_PATH: "/upload"
ATRA_HTTPS_URL: https://mock-atra-https:8443/v1/submit
EXPORT_SCHEMA_DEFAULT: "ATRA_TAP_312_V1"
HOT_RETENTION_DAYS: "30"
volumes:
cdr_pg_data:
cdr_minio_data:
cdr_hsm_data:
cdr_atra_sftp:
Start:
docker compose -f infra/docker/docker-compose.cdr-mediation.yml up -d
# Initialise softhsm + MinIO buckets
docker compose exec softhsm /hsm-init/init.sh
mc alias set local http://localhost:9200 cdrminio cdrminio
mc mb local/cdr-hot local/cdr-cold
mc anonymous set none local/cdr-hot
mc anonymous set none local/cdr-cold
Verify:
curl -s http://localhost:3071/health/ready | jq # ingest
curl -s http://localhost:3072/health/ready | jq # batch
curl -s http://localhost:3073/health/ready | jq # exporter
3. Seed Data
cdr-mediation/seed/seed.sql:
-- Sample tenants + operators (subset)
INSERT INTO cdr.tenants (id, name) VALUES
('tnt_bank_af', 'Bank of Afghanistan'),
('tnt_gov_ndma', 'NDMA'),
('tnt_ecom_01', 'Ecommerce Pilot');
INSERT INTO cdr.operators (id, name, direction_supported) VALUES
('op_awcc', 'AWCC', '{MT,MO}'),
('op_roshan', 'Roshan', '{MT,MO}'),
('op_etisalat_af', 'Etisalat AF', '{MT,MO}'),
('op_mtn_af', 'MTN AF', '{MT,MO}'),
('op_salaam', 'Salaam', '{MT,MO}');
-- Sample regulator destinations
INSERT INTO cdr.regulator_schemas (id, name, schema_version, endpoint_kind, endpoint_uri, active) VALUES
('atra_sftp_v1', 'ATRA SFTP', 'ATRA_TAP_312_V1', 'SFTP', 'sftp://mock-atra-sftp:22/upload', true),
('atra_https_v1', 'ATRA HTTPS', 'ATRA_TAP_312_V1', 'HTTPS', 'https://mock-atra-https:8443/v1/submit', false);
4. Common Commands
4.1 Synthetic DLR publisher
# Publishes 100 sample DLR events to NATS for testing ingest
pnpm --filter @ghasi/cdr-mediation-service dev:synth-dlr -- --count 100 --tenant tnt_bank_af --operator op_awcc
4.2 Trigger hourly rollup manually
# Run rollup for a specific hour
docker compose exec cdr-mediation node dist/apps/batch/rollup.js --hour 2026-04-21T08:00:00Z
4.3 Trigger daily export manually
docker compose exec cdr-mediation node dist/apps/exporter/daily.js --date 2026-04-20
4.4 Inspect the audit chain
psql postgres://cdr:cdr@localhost:5447/cdr -c "
SELECT id, ts, event_type, encode(record_hash, 'hex') as record_hash
FROM cdr.audit ORDER BY ts DESC LIMIT 10;
"
4.5 Verify chain integrity
pnpm --filter @ghasi/cdr-mediation-service audit:verify -- --from 2026-04-01 --to 2026-04-21
4.6 Fetch signed export from SFTP mock
sftp -P 2222 atra@localhost
# then: `ls upload/`, `get upload/cdr-2026-04-20.zip`
# verify signature:
openssl cms -verify -in cdr-2026-04-20.zip.sig -content cdr-2026-04-20.zip -noverify -out /dev/null
4.7 Query CDR via ClickHouse
docker compose exec clickhouse clickhouse-client -q "SELECT COUNT(*), tenant_id FROM cdr.facts_dlr GROUP BY tenant_id"
4.8 Create an adjustment
curl -X POST http://localhost:3071/v1/admin/cdr/adjustments \
-H "Content-Type: application/json" \
-d '{
"originalCdrId": "cdr_abc123",
"reason": "VOID",
"note": "Duplicate reported by tenant"
}'
4.9 Run tests
pnpm --filter @ghasi/cdr-mediation-service test
pnpm --filter @ghasi/cdr-mediation-service test:integration
pnpm --filter @ghasi/cdr-mediation-service test:contract
pnpm --filter @ghasi/cdr-mediation-service test:e2e:local
4.10 Reset state
docker compose -f infra/docker/docker-compose.cdr-mediation.yml down -v
docker compose -f infra/docker/docker-compose.cdr-mediation.yml up -d
docker compose exec softhsm /hsm-init/init.sh
mc alias set local http://localhost:9200 cdrminio cdrminio
mc mb local/cdr-hot local/cdr-cold
5. Environment Variables
Covered in the Docker Compose block above. Additional dev-only:
| Variable | Default | Purpose |
|---|---|---|
DEV_SYNTH_DLR_RATE_PER_SEC | 0 | If > 0, a background thread publishes synthetic DLRs |
EXPORT_DRY_RUN | false | If true, exporter builds + signs but skips SFTP delivery |
CHAIN_VERIFIER_FAIL_FAST | true | Verifier exits on first break (prod: continue + report) |
6. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Ingest receives events but no CDR row | Schema migration pending | pnpm --filter @ghasi/cdr-mediation-service db:migrate |
| Rollup cron never fires | Docker Compose doesn't run Kubernetes CronJobs | Use docker compose exec cdr-mediation node dist/apps/batch/rollup.js manually |
| Export delivery fails with auth error | Mock SFTP credentials changed | Reset volumes; start fresh |
| HSM sign fails | softhsm not initialised | Run /hsm-init/init.sh inside softhsm container |
| ClickHouse insert lag | Containers may have started unevenly | docker compose restart clickhouse |