Skip to main content

consent-ledger-service — Service Overview

Version: 1.0 Status: Draft Owner: Trust & Safety Last Updated: 2026-04-21 References: ADR-0004 §3, 07-epics-and-user-stories.md §6.12, 13-security-compliance-tenancy.md


1. Purpose

consent-ledger-service is the platform's authoritative ledger of subscriber consent and opt-out state for SMS messaging. It owns:

  • The National DND Registry sync (where ATRA maintains a national do-not-disturb list).
  • Per-tenant per-MSISDN opt-in / opt-out records.
  • Inbound MO STOP-keyword processing — translating subscriber-typed STOP/UNSUBSCRIBE/STOPALL/QUIT into consent revocation, propagated to all relevant tenant scopes.
  • A regulator-defensible consent audit log (≥ 7 years, append-only, hash-chained).
  • A CheckConsent gRPC API consumed by compliance-engine and routing-engine so no message reaches a recipient who has revoked consent.

Without this service, the platform would systematically violate Afghan and international consent law (TCPA-equivalent, GDPR Article 7) and would be unable to defend a regulator complaint.


2. Bounded Context

ConcernIn scopeOut of scope
Per-tenant consent recordsRecipient identity / KYC (owned by MNO)
National DND registry syncDND list authorship (owned by ATRA)
Inbound MO STOP-keyword detectionMO transport (owned by smpp-connector / channel-router)
Cross-service consent propagationPer-message blocking (compliance-engine enforces)
Multi-language STOP keywords (Pashto/Dari/Arabic/English)Free-text natural-language opt-out (deferred — Phase 2)
Consent audit log (7 y, append-only, hash-chained)General platform audit (auth.audit_log)

3. Key Responsibilities

  1. Maintain consent.records per (tenantId, msisdn, scope) with state OPT_IN | OPT_OUT | UNKNOWN | EXPIRED.
  2. Daily-pull and merge the National DND Registry from ATRA (where available); cache and apply to all tenants.
  3. Consume inbound MO from channel-router-service / smpp-connector for STOP-keyword matches; revoke consent and emit consent.revoked.v1.
  4. Expose CheckConsent(tenantId, msisdn, scope) → { allowed, reason } gRPC; consumed by compliance-engine (CONSENT rule type) and routing-engine (last-mile veto).
  5. Expose RecordConsent(...) REST for tenants to record explicit opt-in (e.g., from web-form double-opt-in).
  6. Maintain an append-only, hash-chained audit log of every consent state change for ≥ 7 years (regulator evidence window).

4. Dependencies

DirectionDependencyReason
Inboundcompliance-engine (gRPC CheckConsent)Pre-routing consent check
Inboundrouting-engine (gRPC CheckConsent)Last-mile consent veto
Inboundsms-firewall-service (gRPC CheckConsent)Inbound MT firewall consent check
Inboundchannel-router-service / smpp-connector (NATS sms.mo.inbound)STOP-keyword detection
InboundTenant REST RecordConsent, RevokeConsentExplicit opt-in recording
InboundCitizen-facing portal (/consent/{msisdn})Self-service consent inspection
OutboundNATS (consent.granted.v1, consent.revoked.v1, dnd.registry.synced.v1)Downstream notifications
OutboundPostgres (consent schema)Records, audit log
OutboundRedis (consent:state:{tenant}:{msisdn})Hot-cache for CheckConsent (P95 ≤ 5 ms)
OutboundATRA SFTP/APINational DND pull
OutboundObject storage (cold archive, ≥ 7 y)Audit log retention

5. Runtime Topology


6. Key Design Decisions

  1. Fail-closed on CheckConsent cache miss + DB unavailable — when the hot cache misses and Postgres is unreachable, return { allowed: false, reason: "CONSENT_UNKNOWN" }. Better to falsely block one message than to systematically violate consent law.
  2. Scope is first-class — consent is not boolean: it is per (tenantId, msisdn, scope) where scope ∈ {TRANSACTIONAL, MARKETING, OTP, EMERGENCY}. STOP at the marketing scope does not revoke OTP.
  3. National DND overrides everything — the National DND list (when ATRA publishes one) is a hard-block at the platform level, applicable regardless of tenant opt-in claim. Exception: lane=P0 emergency (CBC-bridge has its own opt-out semantics).
  4. Multi-language STOP keywords — recognised opt-out keywords include STOP, STOPALL, UNSUBSCRIBE, QUIT, END, CANCEL (English); بند, لغو, پایان (Dari); بنديدل, لغو, ودرول (Pashto); إلغاء, وقف, إيقاف (Arabic). Configurable; per-tenant overrides allowed only to add additional keywords, not to remove defaults.
  5. Hash-chained audit log — every consent transition row in consent.audit carries prev_hash, payload_hash, record_hash. Regulator can verify integrity end-to-end.
  6. Cold archive at 13 months → 7 y — hot Postgres holds 13 months; older rolled to S3 in monthly partitions. Restoration for regulator queries via async job (≤ 1 h).

7. Surface Inventory

InterfacePurposeAuth
gRPC CheckConsent(tenantId, msisdn, scope) → { allowed, reason, cachedAt }Pre-routing consent check; P95 ≤ 5 msService mesh mTLS
gRPC RecordConsent(tenantId, msisdn, scope, source)Tenant opt-in submissionService mesh mTLS + tenant scope
gRPC RevokeConsent(tenantId, msisdn, scope, reason)Explicit revocationService mesh mTLS + tenant scope
REST POST /v1/consent/recordsTenant API: record opt-inKong JWT
REST DELETE /v1/consent/records/:msisdnTenant API: revokeKong JWT
REST GET /v1/consent/audit?msisdn=Citizen self-service: see who has my consent and when grantedKong JWT (citizen) or admin role
REST GET /v1/admin/consent/dnd-registryDND registry inspectionAdmin role
HTTP /health/live, /health/ready, /metricsK8s + PromNone / cluster
NATS produce consent.granted.v1, consent.revoked.v1, dnd.registry.synced.v1Downstream
NATS consume sms.mo.inboundSTOP-keyword detection

8. Data Ownership

consent schema:

  • consent.records(tenantId, msisdn, scope) PK, state, sourceEvent, validUntil
  • consent.audit — append-only, hash-chained
  • consent.dnd_registry — National DND mirror (per-MSISDN flags, lastSyncAt)
  • consent.stop_keywords — per-language opt-out keyword catalog
  • consent.tenant_scope_config — tenant-defined additional scopes (extensions)

Redis: consent:state:{tenantId}:{msisdn}:{scope} (TTL 300 s).


9. Failure Modes

  • ATRA DND sync stale > 24 h → alert; degraded mode (use last-known DND).
  • STOP-keyword consumer lag > 60 s → alert; subscriber's STOP not honoured promptly is a regulator risk.
  • CheckConsent cache + DB both unavailable → fail-closed (return allowed=false); P0 emergency lane bypass with audit row.
  • Hash-chain break → critical alert; investigation; no consent records discarded.

10. Open Points

IDQuestionOwnerResolution
CONS-OPEN-001National DND registry — does ATRA publish one today?Regulator LiaisonTBD
CONS-OPEN-002Scope catalog — final taxonomy beyond TRANSACTIONAL/MARKETING/OTP/EMERGENCY?T&S CouncilTBD
CONS-OPEN-003Citizen-portal authentication — MNO-confirmed phone-number verification or alternative?Product + MNOTBD
CONS-OPEN-004Cross-tenant STOP — STOP from MSISDN to one tenant should it propagate to all tenants? Defaults to per-tenant; configurable.Legal + ComplianceTentative: per-tenant default