regulator-portal-service — Application Logic
Version: 1.0 Status: Draft Owner: Regulator-facing + Legal Last Updated: 2026-04-21 Companion: DOMAIN_MODEL · API_CONTRACTS · SECURITY_MODEL · FAILURE_MODES
1. Use Cases
UC-01 — RegulatorLogin (mTLS + CRL/OCSP verification)
Trigger: ATRA portal user navigates to https://regulator.ghasi.af/ or programmatic client opens a TLS connection to POST /v1/regulator/*.
Input: TLS client certificate presented during handshake.
Output: Set-Cookie: regulator_session=<opaque>; HttpOnly; Secure; SameSite=Strict plus 200 OK with { userId, orgName, role, allowedRegions }.
SLA: Handshake budget 750 ms (including OCSP); session issuance 50 ms.
Steps:
- TLS termination by the ingress (Envoy with
require_client_certificate: true,verify_certificate_spkipinned to national-PKI trust anchors). - Certificate-chain validation — issuer must be in
regulator_ca_trust_store. RejectSSL_ERROR_BAD_CERT_DOMAINon mismatch. - OCSP check — prefer stapled response (RFC 6066); if absent and
OCSP_HARD_FAIL=true, reject. If stale (>24 h), request fresh OCSP from CA responder (3-of-5 replicas; 500 ms timeout). - CRL check — full CRL refreshed every 15 min in
vault-crl-cache; entry-not-found → soft fail allowed; entry-found-revoked → hard reject withregulator_cert_revoked_totalcounter increment. - Subject-DN lookup —
SELECT user_id, role, allowed_regions FROM regulator.users WHERE cert_subject_dn = $1 AND issuer_dn = $2 AND status = 'ACTIVE'. Not found →403withUNKNOWN_CERT_SUBJECT; the cert is valid but the subject is not provisioned. - Session issue — opaque token in Redis
regulator:session:{token} → userId(TTL 30 min; sliding). - Audit row —
INSERT INTO regulator.users_audit(user_id, action='LOGIN', ip, ua, occurred_at). Append-only. - Emit
regulator.auth.login.v1for SIEM forwarding (see EVENT_SCHEMAS).
Failure behaviour: Hard-fail, never soft-fail. Every failure path writes an audit row with LOGIN_FAILED and a specific sub-reason (CRL_HARD_FAIL, OCSP_UNAVAILABLE, REVOKED, UNKNOWN_SUBJECT, EXPIRED).
UC-02 — SubmitLiRequest
Trigger: POST /v1/regulator/li/requests from an authenticated regulator-li-role user with multipart body { metadata JSON, warrant PDF }.
Input:
{
"targetMsisdn": "+93701234567",
"dateRange": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-21T00:00:00Z" },
"scope": "FULL",
"legalRef": "ATRA-WAR-2026-00127",
"signedWarrantHashSha256": "hex..."
}
Output: 201 Created with { liRequestId, state: "RECEIVED", ackBy, inProgressBy, deliverBy }.
SLA: 500 ms (excluding upload); warrant-hash verification is the critical path.
Steps:
- RBAC gate —
role = regulator-lirequired;role = regulator-readreturns 403. - Schema validation — Zod over
LiRequestCreate. Reject on malformed E.164, scope enum, or missing hash. - Warrant upload — stream to
s3://ghasi-regulator/li/warrants/{liRequestId}.pdfwith object-lockCOMPLIANCEmode, retention 7 years. - Warrant hash verification — compute SHA-256 of the uploaded bytes; compare with
signedWarrantHashSha256. Mismatch → 422WARRANT_HASH_MISMATCHand object is deleted withWARRANT_HASH_MISMATCHaudit row. - Dual-control provision —
initiator = current user;approver = null(waiting). Insert intoregulator.li_requestswith stateRECEIVED; computeackBy = now() + 1h,inProgressBy = now() + 4h,deliverBy = now() + 18h. - Audit chain — compute
hashPrev = latest li_audit.hash_self for this liRequestId(genesis = zeroes);hashSelf = SHA-256(hashPrev || fromState=null || toState=RECEIVED || initiator || approver || rationale). - Outbox publish —
INSERT INTO regulator.outbox(subject='regulator.li.received.v1', payload)in same transaction. - Notify Legal on-call —
notification.sms.outboundwith target+93-oncall-legal, content"LI ${liRequestId} received — ack by ${ackBy}".
UC-03 — AdvanceLiState (dual-control transitions)
Trigger: POST /v1/regulator/li/requests/{liRequestId}/transition from authorised internal actor (Ghasi Legal initiating, Security approving — or vice-versa).
Input: { action: "ACK" | "START" | "DELIVER" | "CLOSE" | "REJECT", rationale?: string, approverSignature?: string }
SLA: 100 ms; dual-control acquisition is the critical constraint, not compute.
Steps:
- Dual-control check — The request carries the initiator JWT; the
approverSignatureis a detached Ed25519 signature over(liRequestId || action || timestamp)by a distinct Security user key retrieved from Vault KV. Same-user signatures are rejected. - State machine validation — verify
(fromState, action)is a valid edge.REJECTonly fromRECEIVEDorACK.CLOSEonly fromDELIVERED. Invalid → 409ILLEGAL_TRANSITION. - SLA check — if
now() > deliverByand action ≠CLOSE/REJECT, emitregulator.li.sla.breached.v1and page Legal + CISO. - Atomic update — in one transaction:
UPDATE regulator.li_requests SET state = <toState>, approver = <approverUserId>, updated_at = now() WHERE li_request_id = $1 AND state = <fromState>(optimistic on state)- Append
LiAuditEntrywith dualinitiator/approver, computed hash chain - Append outbox
regulator.li.transitioned.v1
- On
DELIVER— triggerUC-04 DeliverLiPackageasync (via outbox). TheDELIVEREDstate is not final until SFTP receipt is confirmed.
UC-04 — DeliverLiPackage (signed SFTP delivery)
Trigger: Outbox consumer sees regulator.li.transitioned.v1 with toState = DELIVERED.
Steps:
- Assemble package — via read-through to
cdr-mediation-serviceand (for CC scope) MNO-side intercept tap:iri.json— intercept-related info (signalling events, timestamps, cell-IDs) per ETSI TS 102 232-1 Annex Bcc.csv— communication content events (message bodies, receipts) per scopecover.pdf— human-readable cover letter, court reference, integrity manifest
- Per-file integrity — compute SHA-256 of each file; write
manifest.json={ files: [{name, sha256, bytes}], liRequestId, preparedAt }. - Encrypt package at rest — derive a per-request DEK (AES-256-GCM, random 32 bytes); wrap DEK with the regulator-delivery KEK (Vault Transit
transit/regulator-li-dek). Write ZIP tos3://ghasi-regulator/li/deliveries/{liRequestId}/package.zip.enc. - Sign the ZIP — PKCS#7 detached signature (RFC 5652 CMS SignedData, SHA-256 + RSA-4096) using the
regulator-li-signingkey in HSM. Storepackage.zip.enc.p7s. - SFTP transfer — connect to ATRA drop-box via SSH-2 with fixed host-key pin (
ssh-rsainknown_hosts.pinned). Key exchange preferscurve25519-sha256; MACshmac-sha2-256-etm@openssh.com. Uploadpackage.zip.enc,package.zip.enc.p7s,manifest.json,cover.pdf. - Receipt confirmation — ATRA's drop-box writes a
receipt.txtacknowledging receipt; poll every 60 s for up to 4 h. On receipt:UPDATE regulator.li_requests SET delivery_sftp_receipt_at = now()and emitregulator.li.delivered.v1. - On SFTP failure — exponential backoff (5 retries, 2×/8×/32×/128×/512× seconds); persistent failure →
FAILURE_MODES FM-06runbook.
UC-05 — IngestComplaint
Trigger: POST /v1/regulator/complaints from an ATRA complaints-handler role.
Steps:
- RBAC gate —
regulator-readOR higher. - Schema validation — E.164
citizenMsisdn, enumcomplaintType,summary ≤ 4000 chars,receivedAt ≤ now(). - PII encryption — encrypt full
citizen_msisdnwith tenant-scoped KEK (transit/regulator-complaints); store ciphertext incitizen_msisdn_ciphertext; store masked form (+CCNNN***) in queryable column. - Compute SLA —
slaDueAt = receivedAt + 5 business days(Saturday-Thursday working week for AF). - Optional AI triage — if
FEATURE_COMPLAINT_TRIAGE_AI=true, invoke local classifier (see AI_INTEGRATION); writetriage_ai_categorybest-guess + confidence. - Persist + outbox —
regulator.complaintsrow (stateRECEIVED); outboxregulator.complaint.received.v1. - Workbench signal —
admin-dashboardSSE stream pushes the new complaint to the on-duty triage queue.
UC-06 — GenerateReport (async)
Trigger: POST /v1/regulator/reports (ad-hoc) or cron (scheduled).
Steps:
- RBAC gate —
regulator-reador higher; scheduled jobs run underplatform.regulator.scheduler. - Create
ReportJobwithstatus = PENDING. ReturnreportJobIdimmediately. - Job dispatcher picks it up (BullMQ-style backed by Redis; worker pool = 4):
DAILY_CDR_STATUS→ read-throughcdr-mediation-service/v1/internal/cdr/status(last 90 days); render HTML → PDFMONTHLY_COMPLIANCE_SUMMARY→ aggregatecompliance-engine,consent-ledger-service,sender-id-registry-service,firewall-servicevia their internal REST/gRPCAD_HOC→ filters drive which upstream endpoints are queried
- On upstream partial failure — write a
WARNINGSsection in the report identifying which slices are missing; do not silently omit (regulator-trust preserving). - Sign the PDF — see UC-07.
- Store + outbox —
s3://ghasi-regulator/reports/{yyyy}/{mm}/{reportJobId}.pdf[.p7s]; outboxregulator.report.submitted.v1(for downstream analytics and SIEM).
UC-07 — SignReportPdf (HSM)
Steps:
- HSM handle — open a PKCS#11 session to SoftHSM (dev) or the network HSM (prod); login with
CKU_USERvia token PIN from Vault KV. - Sign —
C_SignInitwithCKM_SHA256_RSA_PKCS_PSS, 4096-bit keyregulator-reports-signing;C_Signover the SHA-256 digest of the PDF bytes. - PKCS#7 envelope — wrap signature in CMS SignedData (RFC 5652) with detached content; include the signing cert chain.
- Store —
<pdf>.p7salongside the PDF. - Key rotation — rotate annually; previous key retained for 10 years for signature verification of historical reports.
On HSM outage, UC-06 marks the job FAILED with error HSM_UNAVAILABLE; see FAILURE_MODES §FM-04.
UC-08 — StreamToSiem (NATS consumer with disk-WAL)
Trigger: Durable NATS consumer regulator-siem-forwarder subscribed to auth.events.*, compliance.audit.v1, consent.*, sender.id.*, cbc.audit.v1, firewall.audit.v1, fraud.detected.*, cdr.exported.v1.
Steps:
- Per-destination formatter — for each enabled
SiemDestination, render the event as CEF, LEEF, or raw JSON (see EVENT_SCHEMAS §4). Formatters are pure functions; unit-tested against ArcSight + QRadar reference docs. - Destination dispatch:
SPLUNK_HEC→POST https://splunk.atra.af:8088/services/collectorwithAuthorization: Splunk {hec_token}; 2xx is ACK.LOGSTASH_HTTP→POST https://logstash.atra.af/ingestwith mTLS client cert; 2xx is ACK.QRADAR_SYSLOG→ TLS-wrapped syslog over:6514; rely on syslog-relay-ACK (RFC 5425 TLS transport).
- Write
SiemDeliveryLog— statusRETRYINGuntil ACK, thenACKED. - NATS ACK — only after all enabled destinations ACK. Destination-scoped DLQ on repeated failure keeps other destinations unblocked.
- Backpressure → disk-WAL — when NATS
num_pendingfor this consumer exceedsSIEM_WAL_THRESHOLD(default 100k) or lag exceeds 1 h, switch to disk-WAL mode:- Consume NATS, append event to append-only file in
/var/lib/regulator/siem-wal/(size-capped 100 GB, rotated by size) - Separate drainer coroutine reads WAL and pushes to destinations at their natural rate
- NATS is ACKed immediately after WAL write (because WAL is durable on EBS)
- Consume NATS, append event to append-only file in
- Recovery — when lag < 5 min and disk-WAL drained, revert to direct mode.
- Emit
regulator.siem.forwarded.v1once per destination ACK.
Budget: Formatter 1 ms; network P95 ≤ 500 ms per destination.
UC-09 — CollectEvidence (scheduled, hourly)
Trigger: Cron 0 * * * * (Asia/Kabul).
Steps:
- For each
AttestationControlwithevidence_typeinAUTO_*:AUTO_SBOM→ readowner_service's/v1/internal/sbomendpoint; SBOM in CycloneDX formatAUTO_CI_RESULT→ query GitHub API for latest main-branch CI runAUTO_ACCESS_REVIEW→ readauth-service/v1/internal/access-reviews?ownerService=XAUTO_IMAGE_SIGNATURE→ verify cosign signatures on running images via cluster admission controller's audit log
- Hash + store — compute SHA-256; write to
s3://ghasi-regulator/evidence/{framework}/{controlId}/{hash}.{ext}. - Update
AttestationEvidence— upsert withstatus = CURRENT,last_review_at = now(). - Stale detection — any row where
last_review_at + 60 days < now()is flipped toSTALE; alert. - Emit
regulator.attestation.evidence.collected.v1.
UC-10 — AuditorLogin (time-boxed)
Trigger: mTLS handshake on https://auditor.ghasi.af/.
Steps:
- Certificate verified against
auditor_ca_trust_store(separate trust anchor from national PKI). - Subject-DN lookup in
regulator.auditor_access— must have agrantedrow withaccessExpiresAt > now(). - Session issued scoped to
grantedFrameworks; all reads audit-logged withauditorId,frameworkAccessed,resourceRef. - On
accessExpiresAtpass, a background sweep invalidates all sessions for that auditor and flips status toEXPIRED.
UC-11 — GenerateAttestationBundle (annual)
Trigger: POST /v1/regulator/attestations/bundle?framework=ISO27001&year=2026 from platform.compliance.admin OR scheduled annual cron.
Steps:
- Collect — fetch all
CURRENTevidence rows for(framework, year); re-verify each file hash against S3 ETag/SHA-256. - Build control matrix — CSV + PDF mapping each
controlId→ evidence files + review timestamps + owner. - Build exec summary — compliance-scorecard dashboard snapshot at year-end.
- Zip —
bundle.zipcontainingcontrol-matrix.csv,control-matrix.pdf,exec-summary.pdf,evidence/directory. - Sign — PKCS#7 over
bundle.zipwithattestation-bundle-signingkey (separate from report-signing key). - Store —
s3://ghasi-regulator/bundles/{framework}/{year}/bundle.zip[.p7s]with permanent object-lock. - Emit
regulator.attestation.bundle.generated.v1.
UC-12 — VerifyRegulatorCert (continuous CRL/OCSP monitoring)
Every 15 min a worker re-validates all ACTIVE regulator users' certificates:
- Pull fresh CRL from each trusted issuer.
- OCSP-query each active cert's status.
- On revocation: flip
status → REVOKED, invalidate all live sessions for thatuserId, emitregulator.cert.revoked.v1, page Regulator-liaison if there are active LI requests in-flight.
2. Sequence Diagrams
LI submission + delivery
SIEM forwarding with disk-WAL fallback
3. Budgets
| Path | P95 budget | P99 ceiling |
|---|---|---|
| Regulator login (handshake → session) | 750 ms | 1500 ms |
| LI submit (excluding warrant upload) | 500 ms | 1000 ms |
| LI transition | 100 ms | 300 ms |
| Complaint ingest | 300 ms | 800 ms |
| Report listing | 200 ms | 500 ms |
| Report download (pre-signed URL issue) | 150 ms | 400 ms |
| Attestation catalog read | 200 ms | 500 ms |
| SIEM forward per destination | 500 ms | 1500 ms |
| Evidence collection cron (per control) | 10 s | 30 s |
| Attestation bundle generation (full) | 10 min | 30 min |
4. Cross-references
- consent-ledger-service — regulator reports include consent-revocation rates; read-through only
- sender-id-registry-service — registry exports for monthly report
- cdr-mediation-service — CDR submission status feed
- compliance-engine — audit log and tenant scores feed reports
- analytics-service — ClickHouse cold-tier for historical windows
- admin-dashboard EP-ADMDASH-10 — complementary workbench UI for triage
- ADR-0004 §3 bounded contexts, §11 HSM, §14 multi-region