Skip to main content

cbc-bridge-service — Event Schemas

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

Companion: DOMAIN_MODEL · SYNC_CONTRACT · SECURITY_MODEL

All events are published to NATS JetStream via the transactional outbox pattern. Every event carries schemaVersion, eventId (UUIDv4), traceId, and at (RFC 3339). Broadcast bodies may appear in events because emergency bodies are public by design; caller identity is internal-confidential and masked for non-platform consumers.


1. Streams, subjects, retention

StreamSubjectsRetentionReplicasDeduplication window
CBC_EVENTScbc.broadcast.requested.v1, cbc.broadcast.dispatched.v1, cbc.broadcast.acked.v1, cbc.broadcast.partial.v1, cbc.broadcast.failed.v1, cbc.broadcast.cancelled.v113 months (regulator)3 (kbl + mzr + dxb leaf)2 min
CBC_AUDITcbc.audit.v113 months hot, 7 years cold35 min
CBC_DRILLcbc.drill.scheduled.v1, cbc.drill.completed.v125 months32 min
CBC_SIGNATUREcbc.signature.audit.v125 months32 min
CBC_HEALTHcbc.mno.adapter.health.v1, cbc.mno.cell.refreshed.v130 days3

All streams have a .deadletter suffix. Consumers are durable with explicit ack per PLT-REQ-008/009.

Cross-region replication: CBC_EVENTS and CBC_AUDIT replicate kbl → mzr synchronously (stream mirror) and kbl → dxb asynchronously (leaf-node audit-only mirror) per ADR-0004 §5.


2. Produced events

2.1 cbc.broadcast.requested.v1

Emitted on successful acceptance of BroadcastEmergency.

{
"$id": "https://schemas.ghasi.io/cbc/broadcast-requested.v1.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["schemaVersion","eventId","broadcastId","callerRef","severity","isDrill","geoTarget","acceptedAt","traceId","at"],
"properties": {
"schemaVersion": { "const": "1" },
"eventId": { "type": "string", "format": "uuid" },
"broadcastId": { "type": "string", "pattern": "^bc_[A-Z0-9]+$" },
"callerRef": {
"type": "object",
"properties": {
"orgName": { "type": "string" },
"callerId": { "type": "string", "pattern": "^caller_[A-Z0-9]+$" },
"certSubject": { "type": "string", "description": "masked on public/partner fan-out" }
}
},
"severity": { "enum": ["P0_EXTREME","P1_MAJOR","P2_ADVISORY"] },
"isDrill": { "type": "boolean" },
"cbsMessageIdentifier": { "type": "integer" },
"geoTarget": {
"oneOf": [
{ "type": "object", "properties": { "kind": { "const": "CELL_IDS" }, "cellCount": { "type": "integer" } } },
{ "type": "object", "properties": { "kind": { "const": "POLYGON" }, "areaKm2": { "type": "number" } } },
{ "type": "object", "properties": { "kind": { "const": "REGION" }, "regionCode": { "type": "string" } } },
{ "type": "object", "properties": { "kind": { "const": "COUNTRY" } } }
]
},
"languageCodes": { "type": "array", "items": { "enum": ["en","fa","ps","ar"] } },
"expectedDispatchBy": { "type": "string", "format": "date-time" },
"acceptedAt": { "type": "string", "format": "date-time" },
"signatureAuditId":{ "type": "string", "format": "uuid" },
"traceId": { "type": "string" },
"at": { "type": "string", "format": "date-time" }
}
}

2.2 cbc.broadcast.dispatched.v1

{
"schemaVersion": "1",
"eventId": "uuid",
"broadcastId": "bc_...",
"mnoIds": ["AWCC","Roshan","Etisalat","MTN_AF","Salaam"],
"adapterKinds": { "AWCC":"STANDARD_3GPP", "MTN_AF":"ERICSSON_PROPRIETARY", "Etisalat":"HUAWEI_PROPRIETARY" },
"cellCountsPerMno": { "AWCC": 1240, "Roshan": 980 },
"dispatchedAt": "RFC3339",
"expectedAckBy": "RFC3339",
"traceId": "...", "at": "..."
}

2.3 cbc.broadcast.acked.v1 / .partial.v1 / .failed.v1

Shared shape:

interface CbcBroadcastFinal {
schemaVersion: '1';
eventId: string;
broadcastId: string;
finalState: 'ACKED' | 'PARTIAL' | 'FAILED';
perMno: Array<{
mnoId: string;
status: 'ACKED'|'FAILED'|'TIMEOUT'|'REJECTED';
adapterKind: 'STANDARD_3GPP'|'ERICSSON_PROPRIETARY'|'HUAWEI_PROPRIETARY';
cellCount: number;
latencyMs: number;
cbeAckReference?: string;
errorCode?: string;
}>;
missingMnoIds: string[]; // non-empty for PARTIAL/FAILED
coveragePct: number; // 0.0..1.0
finalisedAt: string; // RFC 3339
traceId: string;
at: string;
}

2.4 cbc.broadcast.cancelled.v1

interface CbcBroadcastCancelled {
schemaVersion: '1';
eventId: string;
broadcastId: string;
initiatorCallerId: string;
approverCallerId: string;
effectiveCancellations: string[]; // mno ids where cancel reached before ACK
ineffectiveCancellations: string[]; // mno ids that had already ACKED
cancelledAt: string;
traceId: string;
at: string;
}

2.5 cbc.audit.v1

The primary regulator-defensibility feed. One event per state transition (REQUESTED, DISPATCHED, ACKED, PARTIAL, FAILED, CANCELLED, DRILL_EXECUTED).

interface CbcAudit {
schemaVersion: '1';
eventId: string;
auditId: string; // audit_...
broadcastId: string;
transition: 'REQUESTED'|'DISPATCHED'|'ACKED'|'PARTIAL'|'FAILED'|'CANCELLED'|'DRILL_EXECUTED';
prevHash: string; // sha256 hex
rowHash: string; // sha256 hex
snapshotHash: string; // sha256 of canonical JSON of the snapshot (full snapshot not in event — fetched via REST)
actorCallerId: string | null;
actorApproverId: string | null;
occurredAt: string;
traceId: string;
at: string;
}

Why snapshot hash not snapshot: keeps cbc.audit.v1 compact for high-throughput verification; full snapshot is retrievable via GET /v1/cbc/broadcasts/{id}/audit for regulator review.

2.6 cbc.drill.scheduled.v1

interface CbcDrillScheduled {
schemaVersion: '1';
eventId: string;
drillId: string;
cadence: 'MANUAL'|'MONTHLY_FIRST_TUESDAY'|'QUARTERLY';
scheduledAt: string;
geoTarget: { kind: string; /* payload */ };
languageCodes: string[];
traceId: string;
at: string;
}

2.7 cbc.drill.completed.v1

interface CbcDrillCompleted {
schemaVersion: '1';
eventId: string;
drillId: string;
broadcastId: string;
finalState: 'ACKED'|'PARTIAL'|'FAILED';
perMnoLatencyMsP95: number;
cellCountResolved: number;
partialFailureBreakdown: Array<{ mnoId: string; failureCode: string }>;
reportRef: string; // object-store URI of PDF/HTML after-action
traceId: string;
at: string;
}

2.8 cbc.mno.adapter.health.v1

Circuit-breaker transition.

interface CbcMnoAdapterHealth {
schemaVersion: '1';
eventId: string;
mnoId: 'AWCC'|'Roshan'|'Etisalat'|'MTN_AF'|'Salaam';
adapterKind: 'STANDARD_3GPP'|'ERICSSON_PROPRIETARY'|'HUAWEI_PROPRIETARY';
state: 'OPEN' | 'HALF_OPEN' | 'CLOSED';
reason: string; // e.g. '3 consecutive C_Send failures in 60s'
openedAt?: string;
closedAt?: string;
traceId: string;
at: string;
}

2.9 cbc.mno.cell.refreshed.v1

Emitted after a successful per-MNO cell-database atomic swap (UC-09).

interface CbcMnoCellRefreshed {
schemaVersion: '1';
eventId: string;
mnoId: string;
snapshotVersion: number;
rowCount: number;
coveragePctEstimate: number; // derived from synthetic test polygons
refreshedAt: string;
traceId: string;
at: string;
}

2.10 cbc.signature.audit.v1

Emitted for every HSM verify attempt (low-volume, high-value for security forensics).

interface CbcSignatureAudit {
schemaVersion: '1';
eventId: string;
auditId: string;
result:
| 'VERIFIED' | 'SIGNATURE_INVALID'
| 'CERT_EXPIRED' | 'CERT_REVOKED_CRL' | 'CERT_REVOKED_OCSP'
| 'CALLER_NOT_REGISTERED' | 'CALLER_SCOPE_VIOLATION'
| 'HSM_UNAVAILABLE';
presentedCertSubjectMasked: string; // 'CN=****.gov.af'; full only on internal audit channels
pkcs11Operation: 'C_Verify';
sourceIp: string;
observedAt: string;
traceId: string;
at: string;
}

3. Consumed events

SubjectProducerEffect
regulator.ca.trust.updated.v1regulator-portal-serviceReload national-PKI trust chain into HSM slot; refresh OCSP URL (per DOMAIN §5)
sender.id.suspended.v1sender-id-registry-serviceNot consumed — documented for reviewers
consent.dnd.snapshot.v1consent-ledger-serviceNot consumed — CBS is emergency channel (3GPP TS 23.041 §3)

No domain event from outside triggers a broadcast. The service is strictly request-driven.


4. Consumer contracts (by subscribing service)

ServiceSubjectsAction
notification-service (EP-NOTIF-07)cbc.broadcast.acked.v1, cbc.broadcast.partial.v1, cbc.broadcast.failed.v1, cbc.drill.completed.v1Platform portal push for government-client admins; optional webhook fan-out to media partners (CBC-US-017)
analytics-servicecbc.audit.v1, cbc.broadcast.*.v1, cbc.drill.*.v1, cbc.signature.audit.v1Long-term analytics + compliance dashboards
admin-dashboard (SSE)cbc.broadcast.*.v1, cbc.drill.*.v1, cbc.mno.adapter.health.v1Live regulator workbench
regulator-portal-servicecbc.audit.v1, cbc.drill.completed.v1Regulator evidence feed
cbc-bridge-service (self)cbc.adapter.ack.internal.v1 (internal-only stream)UC-04 ack aggregation

5. PII & security rules for events

  • Broadcast bodies are public by design — included in events on platform-internal streams. Public feeds (CBC-US-017) receive a redacted envelope without caller identity.
  • Caller identity is CONFIDENTIAL-INTERNAL. Subjects beyond platform-internal must use presentedCertSubjectMasked (CN=****.gov.af).
  • PKI fingerprints, OCSP responses, HSM slot IDs never appear in any event; they live only in cbc.signature_audit table.
  • Event signing. Every event payload is signed with the platform RS256 key (via JetStream publisher); critical consumers (notification-service, regulator-portal-service) opportunistically verify.
  • Replay safety. eventId is unique; consumers deduplicate by (stream, subject, eventId).

6. Outbox pattern

All state changes write an event row to cbc.outbox in the same PostgreSQL transaction as the aggregate change (e.g. the cbc.broadcasts insert). A relay (CbcOutboxRelay worker) publishes to NATS with retry and explicit ack. This guarantees no emitted event without a persisted state change and no unpublished state change beyond a few seconds.

CREATE TABLE cbc.outbox (
event_id UUID PRIMARY KEY,
subject TEXT NOT NULL,
payload JSONB NOT NULL,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ix_cbc_outbox_unpublished
ON cbc.outbox (created_at) WHERE published_at IS NULL;

7. Retention summary

SubjectRetention classReason
cbc.audit.v1Hot 13 m + Cold 7 y (ADR-0004 §9)Regulator-defensibility
cbc.broadcast.*.v113 mOperational replay + regulator
cbc.drill.*.v125 mCross-review cycle
cbc.signature.audit.v125 mForensic window
cbc.mno.adapter.health.v130 dOperational
cbc.mno.cell.refreshed.v130 dOperational

Retention is managed by JetStream stream configuration + cold archive to MinIO (S3-compatible) for CBC_AUDIT older than 13 months.


8. NATS subject taxonomy (canonical)

cbc.broadcast.requested.v1
cbc.broadcast.dispatched.v1
cbc.broadcast.acked.v1
cbc.broadcast.partial.v1
cbc.broadcast.failed.v1
cbc.broadcast.cancelled.v1
cbc.audit.v1
cbc.audit.chain.verified.v1 (UC-07 success)
cbc.audit.chain.broken.v1 (UC-07 failure — CRITICAL)
cbc.drill.scheduled.v1
cbc.drill.completed.v1
cbc.signature.audit.v1
cbc.mno.adapter.health.v1
cbc.mno.cell.refreshed.v1
cbc.adapter.ack.internal.v1 (internal only — not replicated to dxb)

9. Schema evolution

  • Additive fields with non-required defaults are non-breaking within the same schemaVersion.
  • Breaking changes bump to cbc.<topic>.v2. Subjects coexist during a deprecation window (≥ 90 days); subscribers migrate off v1 before it is removed.
  • Enum values are additive. Consumers MUST treat unknown enum values as UNKNOWN and not fail.