Skip to main content

consent-ledger-service — Jira-Ready Epics & User Stories

Status: populated Owner: Trust & Safety Last updated: 2026-04-21 Service prefix: CONS Scope: Authoritative consent ledger; National DND sync; STOP-keyword processing; consent audit (≥ 7 y); CheckConsent/RecordConsent/RevokeConsent API. Per ADR-0004 §3 and 07-epics-and-user-stories.md §6.12.


Epic Summary

Epic IDTitleStoriesPoints
EP-CONS-01National DND Registry Sync + Per-Tenant Consent RecordsUS-CONS-001 – US-CONS-00629
EP-CONS-02STOP-Keyword Handling (inbound MO → consent revocation propagation)US-CONS-007 – US-CONS-01117
EP-CONS-03Consent Audit Log (regulator-defensible, append-only, ≥ 7 years)US-CONS-012 – US-CONS-01516
EP-CONS-04Consent API for Tenants (CheckConsent / RecordConsent / RevokeConsent)US-CONS-016 – US-CONS-01915
Total19 stories77

Context: Foundation epic. Ledger schema, hot-cache design, ATRA DND sync, double-opt-in flow, citizen self-service.

US-CONS-001 · National DND registry sync from ATRA

Type: Feature | Points: 5

Description: As a Trust & Safety engineer, I need to pull and sync the National DND registry from ATRA daily so that the platform respects the national do-not-disturb list.

Acceptance Criteria:

  • Cron daily 03:00 Asia/Kabul fetches DND list from ATRA endpoint (SFTP / HTTPS)
  • Diff applied to consent.dnd_registry; new entries added, removed entries un-flagged
  • dnd.registry.synced.v1 published with added, removed, total
  • Sync stale > 24 h → alert ConsentDndStale
  • Manual override: POST /v1/admin/consent/dnd/resync

Type: Feature | Points: 3

Description: As a tenant developer, I need to record a consent state for a recipient via REST so that I can attest to my legal opt-in.

Acceptance Criteria:

  • POST /v1/consent/records accepts { msisdn, scope, source: { type, ref, capturedAt }}
  • Record persisted in consent.records; previous record (if any) archived
  • consent.granted.v1 published
  • RLS enforced (tenant-scope only)

US-CONS-003 · Tenant double-opt-in flow

Type: Feature | Points: 5

Description: As a tenant developer, I need a double-opt-in flow where Ghasi sends a confirmation SMS containing an opt-in link so that consent is verifiable.

Acceptance Criteria:

  • POST /v1/consent/double-opt-in/initiate returns optinId + sends opt-in SMS via channel-router
  • GET /v1/consent/double-opt-in/confirm?token= records consent
  • State machine: PENDING → CONFIRMED | EXPIRED (24 h)
  • Confirmation publishes consent.granted.v1 with source.type: "DOUBLE_OPT_IN"

US-CONS-004 · CheckConsent gRPC API (sub-5 ms)

Type: Feature | Points: 8

Description: As compliance-engine / routing-engine / sms-firewall-service, I need to call CheckConsent(tenantId, msisdn, scope) and receive an answer in ≤ 5 ms P95 so pre-routing checks don't slow the pipeline.

Acceptance Criteria:

  • gRPC method returns { allowed: bool, reason: enum, cachedAt }
  • Hot path: Redis hit on consent:state:{tenant}:{msisdn}:{scope}; P95 ≤ 5 ms
  • Cache miss → Postgres read + cache fill (TTL 300 s)
  • Cache + DB unavailable → allowed: false, reason: "CONSENT_UNKNOWN" (fail-closed)
  • National DND match → allowed: false, reason: "NATIONAL_DND" regardless of tenant record
  • Load test: 5000 RPS sustained P99 ≤ 20 ms

Type: Feature | Points: 3

Description: As a Trust & Safety engineer, I need consent recorded per scope so opt-out from MARKETING does not block OTP.

Acceptance Criteria:

  • consent.records PK is (tenantId, msisdn, scope)
  • CheckConsent matches exact scope
  • Default scope when caller omits is TRANSACTIONAL

Type: Feature | Points: 5

Description: As a citizen, I want to see which tenants have my consent and revoke any I no longer want.

Acceptance Criteria:

  • Citizen-portal page /consent/{msisdn} after MSISDN verification (OTP via channel-router)
  • Lists consent records: tenantName, scope, grantedAt, source, status
  • One-click revoke per record; consent.revoked.v1 immediate
  • Audit row written for every view

US-CONS-007 · STOP-keyword detection (multi-language)

Type: Feature | Points: 5

Description: As a T&S engineer, I need STOP/UNSUBSCRIBE keywords detected in English, Pashto, Dari, and Arabic on inbound MO so subscriber-typed opt-outs are honoured immediately.

Acceptance Criteria:

  • NATS consumer for sms.mo.inbound
  • Body trimmed/case-folded; matched against consent.stop_keywords per language
  • Default catalog per SERVICE_OVERVIEW §6.4; tenants may add (not remove)
  • Conformance test: 200 MO samples (50 per language) including legitimate non-STOP words

Type: Feature | Points: 3

Description: As a subscriber, I want a localised ack-back SMS confirming opt-out so I know it was honoured.

Acceptance Criteria:

  • On STOP detection: consent.revoked.v1 published; consent.records updated
  • Localised ack-back via channel-router (matching language); lane=P2 transactional
  • Ack-back is one-shot (no loop)
  • Default scope of revocation: per-tenant (configurable per US-CONS-010)

US-CONS-009 · STOP keyword catalog admin

Type: Feature | Points: 3

Description: As a T&S admin, I need to maintain the STOP-keyword catalog per language so new variants are recognised.

Acceptance Criteria:

  • CRUD on consent.stop_keywords; defaults cannot be removed (only added to)
  • Changes audit-logged
  • Hot reload (no service restart)

US-CONS-010 · Cross-tenant STOP propagation policy

Type: Feature | Points: 3

Description: As a compliance officer, I need to configure whether STOP propagates only to one tenant or all tenants so we balance subscriber rights vs. tenant business needs.

Acceptance Criteria:

  • Platform-wide consent.policy.stop_scope{PER_TENANT, GLOBAL}; default PER_TENANT
  • Citizen-portal "STOP ALL" button forces GLOBAL revocation regardless of policy
  • Policy change is dual-control + audit-logged

US-CONS-011 · STOP-keyword false-positive reporting

Type: Feature | Points: 3

Description: As a tenant, I want to report a STOP-keyword false positive so the keyword catalog improves.

Acceptance Criteria:

  • POST /v1/consent/feedback/false-positive accepts { msisdn, mo, recordedTime }
  • T&S triages via admin dashboard; can re-grant consent on review
  • Pattern aggregation suggests new keyword exclusions

US-CONS-012 · Hash-chained immutable audit log

Type: Feature | Points: 5

Description: As a regulator auditor, I need the consent audit log to be tamper-evident via a hash chain so consent claims are defensible.

Acceptance Criteria:

  • Each consent.audit row carries prev_hash and record_hash = sha256(payload || prev_hash)
  • Cron daily verifies last 24 h chain integrity; alert on break
  • GET /v1/admin/consent/audit/verify?from=&to= returns chain integrity report
  • Postgres trigger rejects UPDATE/DELETE on consent.audit

US-CONS-013 · 13-month hot retention + 7-y cold archive

Type: Feature | Points: 3

Description: As an SRE, I need hot Postgres to hold 13 months and older partitions archived to S3 with 7-y retention so hot DB stays performant.

Acceptance Criteria:

  • Monthly partitions on consent.audit
  • Cron archives partitions > 13 months old to S3 (immutable bucket policy)
  • Restore script for regulator queries; SLA ≤ 1 h

US-CONS-014 · Audit-log query endpoint (regulator)

Type: Feature | Points: 3

Description: As a regulator-portal user, I need to query the audit log by msisdn, tenant, date range so I can answer subscriber complaints.

Acceptance Criteria:

  • GET /v1/admin/consent/audit?msisdn=&tenantId=&from=&to= (mTLS, regulator role)
  • Cursor-paged; max 100 rows/page
  • P95 ≤ 2 s for 13-m hot window
  • Older windows return job ID + S3-restore link

US-CONS-015 · Personal data erasure (GDPR right to erasure)

Type: Feature | Points: 5

Description: As a citizen, I want to request erasure of my consent records so my GDPR-equivalent rights are honoured.

Acceptance Criteria:

  • POST /v1/consent/erasure?msisdn= after MSISDN verification
  • MSISDN replaced by deterministic-hash token in consent.records and consent.audit; original purged
  • National-DND row retained (regulator override)
  • Audit row written consent.erasure.completed.v1
  • SLA: completed within 30 days

US-CONS-016 · RecordConsent gRPC for tenants

Type: Feature | Points: 3

Description: As a tenant developer, I want to call RecordConsent via gRPC mTLS instead of REST for high-volume opt-in capture.

Acceptance Criteria:

  • gRPC RecordConsent with mTLS + tenant scope
  • Same audit + Redis cache fill as REST
  • Bulk variant: RecordConsentBatch accepts up to 1000 entries

US-CONS-017 · RevokeConsent gRPC for tenants

Type: Feature | Points: 2

Description: As a tenant developer, I want to revoke consent programmatically so my customer-data-deletion workflows propagate to Ghasi.

Acceptance Criteria:

  • gRPC RevokeConsent(tenantId, msisdn, scope, reason) — tenant-scope enforced
  • consent.revoked.v1 published with source.type: "TENANT_API"
  • Cache invalidated immediately

US-CONS-018 · Bulk-import opt-in records (legacy migration)

Type: Feature | Points: 5

Description: As a tenant migrating from another platform, I want to bulk-upload opt-in records via CSV so I can move to Ghasi without losing consent provenance.

Acceptance Criteria:

  • POST /v1/consent/bulk-import accepts CSV with msisdn, scope, source_ref, captured_at columns
  • Validation per row; report of accepted/rejected
  • Accepted rows audit-tagged source.type: "BULK_IMPORT" with original CSV hash

Type: Feature | Points: 5

Description: As a developer, I want a Ghasi SDK (Node, Python, Java) for consent operations so integration is fast and idiomatic.

Acceptance Criteria:

  • SDK methods: recordConsent, revokeConsent, checkConsent, bulkImport
  • Auth via API key per developer-portal pattern
  • Examples + docs in dev portal