Skip to main content

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

ToolVersion
Node.js20 LTS
pnpm9.x
Docker Engine24+
psql16
nats CLI0.1.5+
mc (MinIO client)latest
ssh, sftp
pkcs11-toollatest (optional, for softhsm inspection)
jqlatest

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:

VariableDefaultPurpose
DEV_SYNTH_DLR_RATE_PER_SEC0If > 0, a background thread publishes synthetic DLRs
EXPORT_DRY_RUNfalseIf true, exporter builds + signs but skips SFTP delivery
CHAIN_VERIFIER_FAIL_FASTtrueVerifier exits on first break (prod: continue + report)

6. Troubleshooting

SymptomCauseFix
Ingest receives events but no CDR rowSchema migration pendingpnpm --filter @ghasi/cdr-mediation-service db:migrate
Rollup cron never firesDocker Compose doesn't run Kubernetes CronJobsUse docker compose exec cdr-mediation node dist/apps/batch/rollup.js manually
Export delivery fails with auth errorMock SFTP credentials changedReset volumes; start fresh
HSM sign failssofthsm not initialisedRun /hsm-init/init.sh inside softhsm container
ClickHouse insert lagContainers may have started unevenlydocker compose restart clickhouse