Skip to main content

cbc-bridge-service — Domain Model

Version: 1.0 Status: Draft Owner: Government / Emergency Last Updated: 2026-04-21

Companion: SERVICE_OVERVIEW · APPLICATION_LOGIC · DATA_MODEL · EVENT_SCHEMAS · API_CONTRACTS · SECURITY_MODEL Related ADR: ADR-0004 §3 bounded contexts, §11 HSM key custody, §14 multi-region posture


1. Bounded Context

Civil Emergency Cell-Broadcast. The cbc-bridge-service owns every authoritative decision about whether, when, and how an emergency cell-broadcast reaches Afghan handsets — plus the regulator-grade, hash-chained evidence trail that proves it. It is a peer to sms-firewall-service (inbound perimeter), compliance-engine (outbound tenant SMS policy), and consent-ledger-service (subscriber consent). It is not an A2P SMS service and shares nothing with the smpp-connector data plane: cell-broadcast is a distinct 3GPP channel (3GPP TS 23.041 CBS / ETSI EN 302 117) carried by the MNO's Cell Broadcast Entity (CBE), not by MAP/SRI/SubmitSM.

The context boundary is drawn such that:

  • Inside the boundary: emergency-broadcast requests, translation into 3GPP TS 23.041 CBS message PDUs, per-MNO CBE dispatch via vendor adapters, per-MNO acknowledgement aggregation, drill broadcasts, the authorised-caller registry, per-MNO cell-database snapshots for geographic targeting, government-PKI signature-verification evidence, and the hash-chained broadcast audit.
  • Outside the boundary: issuance of the national-PKI itself (held by NDMA / ATRA), subscriber-handset behaviour (out of our control — CBS has no per-subscriber delivery receipt), A2P SMS dispatch (owned by smpp-connector-pool per ADR-0004 §7), map authoring UI (owned by regulator-portal-service per EP-REG-01), human translation authoring (owned by customer-portal localisation glossary per EP-CUST-09), multi-channel non-emergency notifications (owned by notification-service per EP-NOTIF-07).

The service consumes PKI certificate trust material from the HSM and cell-tower coordinate exports from MNOs, but never persists authoritative state for either — it persists only its own verdicts, dispatches, and the evidence underpinning them.


2. Aggregates

2.1 Broadcast

The authoring unit of an emergency cell-broadcast. One Broadcast is one authorised request from a government caller, in one to four language variants, with one geographic target, scheduled for immediate or near-immediate dispatch.

FieldTypeNotes
broadcastIdUUIDv4Identity (external prefix bc_)
callerIdUUIDv4FK → AuthorisedCaller.callerId
headlinestring ≤ 60 charsShort headline; required
bodyVariantsLanguageVariant[]1–4 entries keyed by language code; invariants in §2.1.1
geoTargetGeoTarget VOCell-ID list / polygon / region / country
severityenum SeverityP0_EXTREME · P1_MAJOR · P2_ADVISORY
cbsMessageIdentifierint4370 (P0) / 4371 (P1) / 4372 (P2) / 4373–4379 (test/drill slot)
isDrillbooleanTrue when broadcast is a drill per CBC-US-015
serialNumberint 16-bit3GPP TS 23.041 §9.4.1.2 Serial Number (GeoScope + Msg Code + Update Number)
expiresAtTIMESTAMPTZBroadcast expiry; ≤ acceptedAt + 2h for P0
stateenum BroadcastStateACCEPTEDDISPATCHINGACKED | PARTIAL | FAILED | CANCELLED
signatureAuditIdUUIDv4FK → SignatureAudit.auditId (verifies caller)
prevHashhexSHA-256 of prior broadcast row in partition (hash chain)
recordHashhexSHA-256(canonicalJson(row minus recordHash) ‖ prevHash)
acceptedAt, dispatchedAt, finalisedAtTIMESTAMPTZLifecycle

2.1.1 Invariants

  • PKI signature mandatory. A Broadcast is never persisted without a successful SignatureAudit row recorded via HSM (PKCS#11 C_Verify). In-process verification with a file-mounted public key is prohibited at the code level and enforced by a start-up guard (ADR-0004 §11).
  • Caller-authorisation mandatory. callerId MUST resolve to an AuthorisedCaller whose allowedSeverities contains severity, whose allowedRegions spatially contains geoTarget, and whose validity window (notBefore ≤ now ≤ notAfter) covers acceptedAt. Failure yields PERMISSION_DENIED.
  • Language coverage. P0_EXTREME requires en + fa (Dari) + ps (Pashto). P1_MAJOR requires en + at least one of fa/ps. P2_ADVISORY requires en.
  • Mutually exclusive states. State transitions are one-way: ACCEPTED → DISPATCHING → {ACKED | PARTIAL | FAILED | CANCELLED}. CANCELLED reachable only before per-MNO ACKED is observed for any MNO.
  • Immutable after DISPATCHING. Only state, finalisedAt, and per-MNO dispatch references may change once dispatch has started. bodyVariants, geoTarget, severity, and cbsMessageIdentifier are frozen.
  • Test-range Message Identifier for drills. isDrill = true implies cbsMessageIdentifier ∈ [4373, 4379] (3GPP TS 23.041 Annex A designated test slot). isDrill = false implies cbsMessageIdentifier ∈ [4370, 4372].
  • Serial-number monotonicity. Per (mnoId, cbsMessageIdentifier, geoScope) the Update Number field of the Serial Number MUST be strictly monotonic to allow handsets to dedup per 3GPP TS 23.041 §9.4.1.2.2.

2.2 MnoDispatch

A per-MNO child of a Broadcast representing a single attempt to dispatch the CBS PDU(s) to one MNO CBE endpoint.

FieldTypeNotes
dispatchIdUUIDv4External prefix disp_
broadcastIdUUIDv4FK
mnoIdenumAWCC · Roshan · Etisalat · MTN_AF · Salaam (extensible)
adapterKindenum AdapterKindSTANDARD_3GPP · ERICSSON_PROPRIETARY · HUAWEI_PROPRIETARY
resolvedCellIdsstring[]Cell-ID list produced by GeoTarget resolution using per-MNO CellDatabase
cbsPdusJSONBEncoded per-language CBS PDUs (one per language variant); stored for regulator audit
attemptint1-based; increments on adapter retry
statusenum DispatchStatusPENDING · DISPATCHED · ACKED · FAILED · TIMEOUT · REJECTED
cbeAckReferencestring | nullMNO-assigned ack ID (opaque)
latencyMsintRequest→ack latency
errorCode, errorDetailstring | nullPopulated on FAILED / REJECTED
dispatchedAt, ackedAtTIMESTAMPTZLifecycle

Invariants

  • Exactly one MnoDispatch per (broadcastId, mnoId) unless a retry supersedes a prior row (attempt > 1 and prior row's status ∈ {FAILED, TIMEOUT}).
  • Status transitions are one-way: PENDING → DISPATCHED → {ACKED | FAILED | TIMEOUT | REJECTED}. Re-dispatch increments attempt and creates a new row; the prior row remains for audit.
  • Per-MNO timeout is 30 s (adapter-configurable per MNO contract).

2.3 AuthorisedCaller

A registered government client authorised to submit emergency broadcasts. Authorisation is explicit and auditable, not implicit-by-cert-validity.

FieldTypeNotes
callerIdUUIDv4External prefix caller_
orgNamestringe.g. NDMA, Afghan Civil Defence, Ministry of Interior
certSubjectstringRFC 4514 DN of the allowed national-PKI client certificate
certFingerprintSha256hexAdditional pin on the caller's end-entity cert
allowedSeveritiesenum Severity[]e.g. [P1_MAJOR, P2_ADVISORY] — only NDMA may carry P0_EXTREME
allowedRegionsstring[]Named provinces/districts or ISO-3166-2 codes (AF, AF-KAB, AF-BAL)
mouRefstringInter-agency MOU reference (binds legal authority)
notBefore, notAfterTIMESTAMPTZValidity window
dualControlPartnersUUIDv4[]callerIds that may co-approve cancellations/severity-escalations
activebooleanSoft-disable without deletion
createdAt, deactivatedAtTIMESTAMPTZLifecycle

Invariants

  • (certSubject) is unique among active = TRUE rows.
  • Mutations produce an append-only authorised_callers_history row (never silently overwrite).
  • A caller MUST have mouRef — broadcasts without legal basis are architecturally refused.

2.4 CellDatabase

A per-MNO snapshot of cell-tower coordinates used for polygon/region → Cell-ID resolution.

FieldTypeNotes
mnoIdenumAs §2.2
cellIdstringMNO-assigned Cell Global Identity (MCC+MNC+LAC+CI per 3GPP TS 23.003 §4.3.1)
lat, lngnumeric(9,6)WGS 84
accuracyMetersintFrom MNO export; used for polygon edge handling
lastUpdatedAtTIMESTAMPTZPer-row updated from weekly MNO export
snapshotVersionintPer-MNO snapshot version; allows rollback to last-known-good

Invariants

  • A snapshot ingest is atomic per MNO: either the entire export is applied (with a new snapshotVersion) or none.
  • A GeoTarget polygon is resolved at dispatch time against the current snapshot; the resolved resolvedCellIds is frozen into the MnoDispatch row so audit evidence survives subsequent snapshot refreshes.
  • Coverage report per MNO: % of national-area polygons resolvable — alert below 85% per-MNO.

2.5 Drill

A scheduled drill broadcast, distinct from a real emergency at the state-machine level.

FieldTypeNotes
drillIdUUIDv4External prefix drill_
broadcastIdUUIDv4 | nullPopulated once the associated Broadcast row is created
cadenceenumMANUAL · MONTHLY_FIRST_TUESDAY · QUARTERLY
scheduledAtTIMESTAMPTZNext scheduled execution
geoTargetGeoTargetTypically national
bodyVariantsLanguageVariant[]Drill body, prefixed with localised "DRILL — NO ACTION REQUIRED"
afterActionReportRefstring | nullObject-store URI once dispatched
stateenumSCHEDULED · EXECUTED · SKIPPED_HOLIDAY · CANCELLED

Invariants

  • A Drill's resulting Broadcast MUST carry isDrill = true and cbsMessageIdentifier in the CBS test slot (4373..4379).
  • A Drill never emits regulator.atra.* downstream events (drills are internal exercises).
  • Two consecutive missed scheduled drills trigger CbcDrillOverdue (MEDIUM).

2.6 BroadcastAuditEntry

Append-only, hash-chained evidence record for every broadcast state transition (request, dispatch, ack, partial, failed, cancelled). This is the primary regulator-defensible evidence feed.

FieldNotes
auditIdUUIDv4 (external prefix audit_)
broadcastIdFK
transitionenum AuditTransitionREQUESTED · DISPATCHED · ACKED · PARTIAL · FAILED · CANCELLED · DRILL_EXECUTED
prevHashSHA-256 of the previous row in the partition
rowHashSHA-256(canonicalJson(row minus rowHash) ‖ prevHash)
snapshotJSONB — broadcast + per-MNO dispatches at this instant
actorCallerId, actorApproverIdWho triggered the transition
occurredAtTIMESTAMPTZ (partition key, monthly RANGE)

Invariants

  • Append-only at the database level. UPDATE and DELETE rejected by Postgres rules (see DATA_MODEL §3.1).
  • prevHash MUST equal the prior row's rowHash within the partition. A break is a CRITICAL alert (CbcAuditChainBroken).
  • Retention: ≥ 13 months hot, ≥ 7 years cold (regulator obligation per ADR-0004 §9).

2.7 SignatureAudit

Every national-PKI signature verification result — success or failure — is written here. This exists so probing, misconfiguration, and revocation effects are all observable.

FieldNotes
auditIdUUIDv4
presentedCertSubjectstring (DN)
presentedCertFingerprintSha256hex
signatureAlgorithmstring (e.g. SHA256withRSA)
resultenum VerifyResultVERIFIED · SIGNATURE_INVALID · CERT_EXPIRED · CERT_REVOKED_CRL · CERT_REVOKED_OCSP · CALLER_NOT_REGISTERED · CALLER_SCOPE_VIOLATION · HSM_UNAVAILABLE
pkcs11Operationstring (e.g. C_Verify)
evidenceJSONB — OCSP response hash, CRL age, HSM slot id
observedAtTIMESTAMPTZ

Invariants

  • Append-only. Retention 25 months (covers two regulator review cycles).

3. Value Objects

VOShapeInvariants
Severityenum P0_EXTREME · P1_MAJOR · P2_ADVISORYP0_EXTREME requires NDMA-tier caller; P1→P0 escalation requires dual-approver per CBC-US-014
GeoTarget`{ kind: CELL_IDSPOLYGON
CbsMessageIdentifierint4370=P0, 4371=P1, 4372=P2, [4373..4379]=test (3GPP TS 23.041 Annex A)
LanguageVariant`{ lang: 'en''fa'
CbsSerialNumber16-bit intBits 0–3 GeoScope, 4–13 Msg Code, 14–15 Update Number (3GPP TS 23.041 §9.4.1.2.2)
CbsPduPage{ pageNo: int, pageCount: int, contentHex: string }pageCount ≤ 15 per spec; pageNo ∈ [1..pageCount]
BroadcastStateenumACCEPTED · DISPATCHING · ACKED · PARTIAL · FAILED · CANCELLED
DispatchStatusenumPENDING · DISPATCHED · ACKED · FAILED · TIMEOUT · REJECTED
CallerCertPin{ subject, sha256Fingerprint }Used to pin the exact end-entity cert against the PKI chain

4. Domain Events (produced)

Detailed JSON Schemas in EVENT_SCHEMAS.md. All events carry schemaVersion, eventId, traceId, at, and are emitted via the transactional outbox pattern.

EventTriggerCore payload
cbc.broadcast.requested.v1Verified BroadcastEmergency persists ACCEPTED rowbroadcastId, callerId (masked on partner fan-out), severity, geoTarget, isDrill
cbc.broadcast.dispatched.v1state transitions ACCEPTED → DISPATCHINGbroadcastId, mnoIds[], expectedAckByMs
cbc.broadcast.acked.v1Final state ACKEDbroadcastId, perMno (mno → ACKED/FAILED/TIMEOUT), coveragePct
cbc.broadcast.partial.v1Final state PARTIALAs above, plus missingMnoIds[]
cbc.broadcast.failed.v1Final state FAILEDbroadcastId, perMno, reasonCode
cbc.broadcast.cancelled.v1Dual-control cancellation acceptedbroadcastId, initiatorCallerId, approverCallerId, effectiveCancellations, ineffectiveCancellations
cbc.audit.v1Every BroadcastAuditEntry appendauditId, broadcastId, transition, rowHash
cbc.drill.scheduled.v1Drill scheduled (cron or manual)drillId, scheduledAt, geoTarget
cbc.drill.completed.v1Drill's underlying broadcast finaliseddrillId, broadcastId, reportRef
cbc.mno.adapter.health.v1Adapter circuit-breaker opens or closesmnoId, adapterKind, state (OPEN/CLOSED), reason
cbc.signature.audit.v1Every SignatureAudit append (low-volume)auditId, result, presentedCertSubject (masked below platform.cbc.admin)

PII guardrails — broadcast bodies are public by design (everyone in the targeted area will read them), so bodies may appear in events. However, caller identity (cert subject) is internal-confidential and appears only on streams consumed by platform-internal subscribers; the public-facing status.ghasi.io/cbc-drills feed receives a redacted view.


5. Consumed Events

SubjectProducerEffect
regulator.ca.trust.updated.v1regulator-portal-serviceReload the national-PKI trust chain into the HSM slot; refresh OCSP responder URL
sender.id.suspended.v1sender-id-registry-serviceIgnored (CBC is not a sender-ID channel); documented so cross-referencing reviewers don't expect it
consent.dnd.snapshot.v1consent-ledger-serviceNOT consumed. CBS is an emergency channel — DND does not apply (3GPP TS 23.041 §3 priority handling). Documented explicitly so reviewers don't expect filtering.
fraud.detected.*fraud-intel-serviceNot consumed — fraud intel does not apply to inbound government broadcasts

6. Global Invariants

  • Fail-closed for authentication, fail-open per-MNO for dispatch. Every broadcast requires successful HSM-backed PKI signature verification (fail-closed). Once past authentication, dispatch fans out to all MNO CBEs in parallel; any individual MNO failure yields PARTIAL (fail-open per-MNO) rather than blocking on the slowest MNO (CBC-US-004).
  • Append-only audit with hash-chain continuity. cbc.broadcasts and cbc.audit reject UPDATE/DELETE at the DB level. A break in the prevHash → rowHash chain is a CRITICAL alert.
  • Dual-control on cancellation and severity escalation. Cancelling an in-flight broadcast or escalating P1→P0 requires a second AuthorisedCaller.callerId listed in the initiator's dualControlPartners within a 60-second window (CBC-US-005, CBC-US-014).
  • Drill mode is first-class. Drills are distinguishable at every layer: CBS Message Identifier in test slot, isDrill=true on Broadcast row, cbc.drill.* event subjects, no ATRA export, "DRILL — NO ACTION REQUIRED" localised prefix (CBC-US-015).
  • Replay-attack window. Each BroadcastEmergency signature is bound to a nonce + requestedAt (RFC 3339, UTC). A signature is accepted only if |now - requestedAt| ≤ 120s and (presentedCertFingerprint, nonce) has not been seen in the last 24 h (Redis cbc:nonce:{fp}:{nonce} with 24 h TTL).
  • Body confidentiality is irrelevant for broadcast content (public by nature). Body confidentiality is relevant for the AuthorisedCaller registry (certFingerprint, mouRef) and SignatureAudit evidence (role-gated to platform.cbc.admin / platform.auditor).
  • Per-MNO cell database is authoritative at dispatch time. The resolvedCellIds frozen on MnoDispatch is the single source of truth for "which cells did this broadcast reach" — subsequent MNO cell-DB refreshes do not retroactively change audit evidence.

7. State Machines

7.1 Broadcast State Machine

┌───────────┐
BroadcastEmergency ──▶ │ ACCEPTED │
(HSM verified) └─────┬─────┘
│ dispatcher picks up

┌──────────────┐
│ DISPATCHING │
└──┬─┬─┬─┬─────┘
│ │ │ │
ack-aggregator tallies per-MNO dispatches
│ │ │ │
┌──────ALL ACKED─────┐
│ │
▼ │
┌──────────┐ │
│ ACKED │ │
└──────────┘ │
│ mixed outcomes

┌──────────┐
│ PARTIAL │
└──────────┘
│ all MNOs failed

┌──────────┐
│ FAILED │
└──────────┘
dual-control CancelBroadcast (before any MNO ACKED)


┌─────────────┐
│ CANCELLED │
└─────────────┘

7.2 MnoDispatch State Machine

PENDING ──dispatcher send──▶ DISPATCHED
│ │
│ ├─ CBE ack ──▶ ACKED
│ ├─ CBE nack ──▶ REJECTED
│ ├─ 30 s no-ack ──▶ TIMEOUT
│ └─ network err ─▶ FAILED

7.3 AuthorisedCaller State Machine

(admin create) (admin revoke) (notAfter passes)
─────────────▶ active ───────────────▶ deactivated ───▶ archived-history

CRL/OCSP revocation observed

auto-disable (active=FALSE) + PagerDuty

8. Aggregate Lifecycle Summary

AggregateCreated byMutated byRetired by
BroadcastBroadcastEmergency gRPC handler (after HSM verify)Ack aggregator (state); dual-control cancel (state=CANCELLED)Partition pruned 13 months hot / 7 years cold
MnoDispatchDispatcher worker on DISPATCHING transitionAdapter on CBE responseRetained with parent; retry creates new row
AuthorisedCallerPlatform admin REST (+ Legal sign-off of mouRef)Admin edit (new history row appended)Soft-delete (active=FALSE); never hard-deleted
CellDatabaseWeekly MNO export ingest (or admin upload)Atomic replace per MNO snapshotPrior snapshots retained 90 d for rollback
DrillCron scheduler or admin RESTScheduler advances next scheduledAt after executionRetained with underlying Broadcast row
BroadcastAuditEntryAppend on every state transitionImmutablePartition pruned per retention
SignatureAuditAppend on every HSM verify attemptImmutable25-month retention

9. Cross-Service References

  • regulator-portal-service is the only non-government caller for some flows (regulator observation of drills per EP-REG-01). It is also the producer of regulator.ca.trust.updated.v1 that refreshes the national-PKI trust chain.
  • customer-portal EP-CUST-09 owns the localisation glossary; drill/broadcast body template authoring in the admin UI pulls approved translations from the glossary.
  • admin-dashboard EP-ADMDASH-10 hosts the regulator workbench surface for GET /v1/cbc/broadcasts and the hash-chain verifier view.
  • notification-service EP-NOTIF-07 subscribes to cbc.broadcast.acked.v1 for non-handset distribution (platform portal push + media-partner webhook of drill records per CBC-US-017).
  • ADR-0004 §11 binds the PKI verification path to the HSM (PKCS#11 C_Verify). §14 (multi-region posture) makes broadcasts region-local and audit globally mirrored — see SYNC_CONTRACT.md §5.

10. Open Points

IDQuestionOwner
DM-OPEN-01Extension of AuthorisedCaller.allowedRegions to ISO-3166-2 for cross-border DR drills (Tajik / Uzbek partner exercises)?Legal + NDMA
DM-OPEN-02Whether isDrill=true broadcasts should also populate cbc.audit.v1 or a separate cbc.drill.audit.v1 subjectTrust & Safety
DM-OPEN-03Whether Update-Number roll-over (2-bit → max 3 updates per (MsgCode, GeoScope)) requires a Msg Code auto-increment policyMessaging Core