Skip to main content

cbc-bridge-service — Sync Contract

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

Companion: API_CONTRACTS · EVENT_SCHEMAS · DATA_MODEL · DEPLOYMENT_TOPOLOGY Related ADR: ADR-0004 §5 multi-region topology

This document defines what other services depend on from cbc-bridge-service, what it depends on from others, and the per-aggregate conflict & replication policies.


1. Consumers of cbc-bridge-service

CallerInterfaceDependency typeSLA expectation
Government clients (civil defence, NDMA, police)gRPC BroadcastEmergency / CancelBroadcast / GetBroadcastStatus over mTLS + national-PKI signatureSynchronous (low-volume, high-severity)P95 ≤ 1 500 ms; availability 99.99% during declared emergencies
regulator-portal-servicegRPC BroadcastEmergency (on behalf of regulator)SynchronousSame
admin-dashboardHTTPS REST /v1/cbc/*Synchronous admin surfaceP95 ≤ 500 ms; availability 99.5%
notification-serviceNATS consumer (cbc.broadcast.*, cbc.drill.*)Async fan-outEventually consistent within 5 s
analytics-serviceNATS consumer (cbc.audit.v1, others)Async analyticsEventually consistent within 60 s
Media partners (CBC-US-017)HMAC-signed webhook from notification-service (public drill feed)AsyncBest-effort

Sync contract semantics

The gRPC call is synchronous from the caller's perspective. Return implies:

  • Signature was HSM-verified (persisted to cbc.signature_audit)
  • Caller authorised by registry
  • Broadcast row persisted with hash chain computed
  • cbc.broadcast.requested.v1 scheduled for outbox publish within 1 s

Dispatch (CBE send + ack) is asynchronous. The caller polls GetBroadcastStatus or subscribes to cbc.broadcast.*.v1 for the final verdict.

Fail-closed for authentication. Fail-open per-MNO for dispatch. Any HSM/PKI failure rejects the submission. Once accepted, dispatch fans out to all MNOs; one MNO's failure yields PARTIAL rather than blocking others (DOMAIN §6).


2. Dependencies of cbc-bridge-service

DependencyInterfaceFailure mode if unavailable
PostgreSQL cbc schemaSQL via pgbouncer (transaction pooling)Handler returns CBC_DEPENDENCY_UNAVAILABLE 503; caller retries; no audit gap (fail-closed)
Redis (DB 5)SET/GET/ZADD/SETNXIdempotency + replay detection fall back to DB (UC-01 nonce_audit table); latency up; if DB also unavailable, fail-closed
NATS JetStreamPublish via outbox relayPublish retried in relay; state is authoritative in Postgres
HSM (PKCS#11)C_Verify per broadcastFail-closed. All BroadcastEmergency rejected with CBC_HSM_UNAVAILABLE. Manual fallback per FAILURE_MODES FM-01
Vault (AppRole)Per-MNO CBE credentials, HSM slot unsealingPods cannot boot without Vault bootstrap; running pods cache creds 15 min
MNO CBE endpointsPer-adapter TCP + vendor protocol over IPSec/leased linkPer-MNO FAILED/TIMEOUT; fail-open per-MNO → PARTIAL or FAILED
OCSP responderHTTPSCRL cache (4 h) serves; cache miss + OCSP unreachable → fail-closed reject
CRL publication URLHTTPS (per national-PKI CA)Redis cache serves; refresh retries
regulator-portal-service producing regulator.ca.trust.updated.v1NATSCurrent trust chain continues; alert if > 24 h stale

3. Proto definition

syntax = "proto3";
package ghasi.sms.cbc.v1;
option go_package = "github.com/ghasi/sms-gateway/cbc/v1";

service CbcBridgeService {
rpc BroadcastEmergency(BroadcastEmergencyRequest)
returns (BroadcastEmergencyResponse);
rpc GetBroadcastStatus(GetBroadcastStatusRequest) returns (BroadcastStatus);
rpc CancelBroadcast(CancelBroadcastRequest) returns (CancelBroadcastResponse);
rpc ScheduleDrill(ScheduleDrillRequest) returns (ScheduleDrillResponse);
rpc VerifyAuthorisedCaller(VerifyAuthorisedCallerRequest)
returns (VerifyAuthorisedCallerResponse);
}

// See API_CONTRACTS.md §1 for full message shapes.

Proto package is ghasi.sms.cbc.v1. Breaking changes bump to v2 with a ≥ 90-day deprecation window in parallel.


4. Per-Aggregate Conflict Policy

The service writes authoritative state for each aggregate in a single region (region-pinned). Cross-region conflict is only possible under a split-brain scenario after manual failover; the policy per aggregate is:

AggregatePolicyRationale
Broadcastserver_authoritative (region-pinned)A broadcast originates in the region currently holding the active gRPC listener for its caller. Failover is human-gated per ADR-0004 §5 to prevent split-brain on broadcastId uniqueness.
MnoDispatchserver_authoritative (region-pinned to same region as parent Broadcast)Tied to the parent's region; never written in two regions simultaneously.
AuthorisedCallerserver_authoritative with lww (last-write-wins) on UPDATE, append-only on authorised_callers_historyRegistry is platform-wide; multi-master with logical replication per ADR-0004 §5. LWW on updated_at is safe because mutations are rare and dual-control is via dualControlPartners field.
CellDatabasePer-MNO snapshot_version monotonic; cross-region mirrorOnly the primary region runs the weekly cron; mirror is read-only. Manual failover bumps the lineage.
Drillserver_authoritativeScheduler runs in primary region only; secondary region promotes only on failover.
BroadcastAuditEntryappend_only; cross-region mirrorHash-chain within a partition requires serial append; mirror is read-only. Under split-brain, each region's chain remains internally valid; reconciliation creates a new chainLineageId and appends both chains under a superposition row.
SignatureAuditappend_only; cross-region mirrorSame as audit.
outboxappend_onlyRelay idempotency via event_id ensures duplicate publish is harmless.

Any split-brain event is a CRITICAL incident (IR-CBC-{date}) and triggers a manual reconciliation runbook — the platform deliberately sacrifices automatic reconciliation to preserve audit defensibility.


5. Cross-Region Replication

Per ADR-0004 §5:

Data classReplicationConsistency
cbc.broadcasts, cbc.mno_dispatchesLogical replication kbl → mzr (async, RPO ≤ 5 s)Region-local primary
cbc.auditLogical replication kbl → mzr and JetStream mirror kbl → dxb (audit-only leaf)Append-only; lag alerted
cbc.signature_auditSame as audit
cbc.authorised_callersLogical replication multi-master kbl ↔ mzrConflict-free under LWH; rare mutations
cbc.mno_cell_databaseSnapshot ship kbl → mzr + S3 mirrorWeekly cron writes only in primary
cbc.drillsLogical replication kbl → mzrScheduler only in primary
cbc.outboxNot replicated — region-localEach region relays its own outbox to the co-located JetStream cluster
cbc.nonce_auditNot replicated — region-localReplay detection is per-region (Redis + local table)

Failover posture.

  • Read failover is automatic for admin/regulator GET surfaces (Cloudflare + GeoDNS).
  • Write failover (BroadcastEmergency) is manual-gated — split-brain on broadcastId uniqueness is unacceptable for audit. A declared failover reassigns the writer region and announces the new active listener to all government callers via a pre-shared mTLS SNI and DNS update.
  • The dxb leaf is strictly an audit-mirror; no writes are accepted there.

6. Integration Point — Government Caller mTLS

Government clients present a national-PKI client certificate. The gRPC server:

  1. Accepts only certs chained to the configured national-PKI root (pinned by SHA-256 of issuer-cert DER).
  2. Validates the end-entity cert against (certSubject, certFingerprintSha256) tuples in cbc.authorised_callers.
  3. Checks OCSP stapling on every handshake + CRL cache every 4 h (CBC-US-008).
  4. Rejects connections failing any of the above at TLS layer (TLS 1.3 bad_certificate alert); the application handler never runs.
# nginx/istio-style illustration (actual implementation is gRPC + SPIFFE for platform peers, national-PKI for gov callers)
server_ssl_client_verify on;
server_ssl_client_trusted_cert_file /etc/cbc-tls/nat-pki-root.pem;
server_ssl_client_verify_depth 3;
server_ssl_stapling on;
server_ssl_stapling_verify on;
server_ssl_verify_crl /etc/cbc-tls/crl-cache.pem;

7. MNO CBE Adapter Contract

Each adapter implements a narrow Go/TS interface; this is what we guarantee to every MNO integration:

interface CbeAdapter {
/** Send one or more CBS PDUs to the MNO CBE endpoint.
* Returns an opaque ack reference when the CBE accepts (not necessarily when it broadcasts).
* Must complete within `deadline` or throw `TIMEOUT`.
*/
send(params: {
mnoBindId: string;
cbsPdus: CbsPduPage[]; // per-language, per-page
cellIds: string[];
serialNumber: number;
messageIdentifier: number; // 4370..4379
deadline: Date; // default now() + 30s
traceId: string;
}): Promise<{ cbeAckReference: string; latencyMs: number }>;

/** Attempt best-effort cancellation of a previously accepted broadcast.
* Returns true if the CBE confirms cancel; false/throw if already broadcasting.
*/
cancel(params: { mnoBindId: string; cbeAckReference: string }): Promise<boolean>;

/** Consume async ack from CBE (webhook or callback connection).
* Emits `cbc.adapter.ack.internal.v1` for UC-04 aggregator.
*/
handleAckCallback(raw: unknown): Promise<void>;

/** Health probe — used by circuit breaker and readiness. */
health(): Promise<{ ok: boolean; latencyMs: number }>;
}

Implementations:

  • Standard3gppCbeAdapter — 3GPP TS 23.041 §10 procedure, wire-format per ETSI EN 302 117. Used for AWCC, Roshan.
  • EricssonProprietaryCbeAdapter — Ericsson CBC "CBC-NBI" North-Bound Interface, XML over HTTPS. Used for MTN_AF.
  • HuaweiProprietaryCbeAdapter — Huawei USPP-CBE protocol, JSON over HTTPS with HMAC-SHA256 per message. Used for Etisalat, Salaam.

New MNO = implement a new adapter + register in cbc.mno_bind_registry + populate cbc.cbe_credentials_ref row.


8. Outbox / Inbox patterns

Outbox (produced). Every DB transaction that mutates cbc.broadcasts, cbc.mno_dispatches, cbc.audit, cbc.signature_audit, cbc.drills, cbc.authorised_callers, or cbc.mno_cell_db_current inserts a corresponding row into cbc.outbox within the same transaction. The CbcOutboxRelay worker reads unpublished rows, publishes to JetStream with explicit ack, and marks published_at. Failures retry; duplicate publish is safe because consumers dedupe on eventId.

Inbox (consumed). regulator.ca.trust.updated.v1 arrives on a durable consumer. The handler:

  1. Reads new trust anchor payload (signed by regulator HSM).
  2. Verifies against the previous trust anchor's signature to prevent malicious rotation.
  3. Writes to Vault secret/cbc/pki/trust-chain.
  4. Signals the HSM trust-store reloader.
  5. Acks only after success.

9. Schema stability guarantees

gRPC

FieldStability
BroadcastEmergencyRequest.* required fieldsStable
Severity enumStable; additions non-breaking (clients MUST treat unknown as UNSPECIFIED)
GeoTarget.kindStable
New optional fieldsNon-breaking (proto3 semantics)

REST

  • Routes under /v1/cbc/* maintain backwards compatibility within the major version.
  • Breaking changes require /v2/cbc/* with Sunset header on the legacy path.

Events


10. Contract tests

  • Pact tests verify regulator-portal-service → cbc-bridge-service BroadcastEmergency contract on every CI run.
  • Generated OpenAPI is diffed against previous snapshot; breaking changes require an ADR and the /v2/ prefix pattern.
  • gRPC reflection test ensures every v1 field survives code changes.