Skip to main content

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:

  1. TLS termination by the ingress (Envoy with require_client_certificate: true, verify_certificate_spki pinned to national-PKI trust anchors).
  2. Certificate-chain validation — issuer must be in regulator_ca_trust_store. Reject SSL_ERROR_BAD_CERT_DOMAIN on mismatch.
  3. 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).
  4. CRL check — full CRL refreshed every 15 min in vault-crl-cache; entry-not-found → soft fail allowed; entry-found-revoked → hard reject with regulator_cert_revoked_total counter increment.
  5. Subject-DN lookupSELECT user_id, role, allowed_regions FROM regulator.users WHERE cert_subject_dn = $1 AND issuer_dn = $2 AND status = 'ACTIVE'. Not found → 403 with UNKNOWN_CERT_SUBJECT; the cert is valid but the subject is not provisioned.
  6. Session issue — opaque token in Redis regulator:session:{token} → userId (TTL 30 min; sliding).
  7. Audit rowINSERT INTO regulator.users_audit(user_id, action='LOGIN', ip, ua, occurred_at). Append-only.
  8. Emit regulator.auth.login.v1 for 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:

  1. RBAC gaterole = regulator-li required; role = regulator-read returns 403.
  2. Schema validation — Zod over LiRequestCreate. Reject on malformed E.164, scope enum, or missing hash.
  3. Warrant upload — stream to s3://ghasi-regulator/li/warrants/{liRequestId}.pdf with object-lock COMPLIANCE mode, retention 7 years.
  4. Warrant hash verification — compute SHA-256 of the uploaded bytes; compare with signedWarrantHashSha256. Mismatch → 422 WARRANT_HASH_MISMATCH and object is deleted with WARRANT_HASH_MISMATCH audit row.
  5. Dual-control provisioninitiator = current user; approver = null (waiting). Insert into regulator.li_requests with state RECEIVED; compute ackBy = now() + 1h, inProgressBy = now() + 4h, deliverBy = now() + 18h.
  6. 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).
  7. Outbox publishINSERT INTO regulator.outbox(subject='regulator.li.received.v1', payload) in same transaction.
  8. Notify Legal on-callnotification.sms.outbound with 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:

  1. Dual-control check — The request carries the initiator JWT; the approverSignature is a detached Ed25519 signature over (liRequestId || action || timestamp) by a distinct Security user key retrieved from Vault KV. Same-user signatures are rejected.
  2. State machine validation — verify (fromState, action) is a valid edge. REJECT only from RECEIVED or ACK. CLOSE only from DELIVERED. Invalid → 409 ILLEGAL_TRANSITION.
  3. SLA check — if now() > deliverBy and action ≠ CLOSE/REJECT, emit regulator.li.sla.breached.v1 and page Legal + CISO.
  4. 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 LiAuditEntry with dual initiator/approver, computed hash chain
    • Append outbox regulator.li.transitioned.v1
  5. On DELIVER — trigger UC-04 DeliverLiPackage async (via outbox). The DELIVERED state 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:

  1. Assemble package — via read-through to cdr-mediation-service and (for CC scope) MNO-side intercept tap:
    • iri.json — intercept-related info (signalling events, timestamps, cell-IDs) per ETSI TS 102 232-1 Annex B
    • cc.csv — communication content events (message bodies, receipts) per scope
    • cover.pdf — human-readable cover letter, court reference, integrity manifest
  2. Per-file integrity — compute SHA-256 of each file; write manifest.json = { files: [{name, sha256, bytes}], liRequestId, preparedAt }.
  3. 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 to s3://ghasi-regulator/li/deliveries/{liRequestId}/package.zip.enc.
  4. Sign the ZIP — PKCS#7 detached signature (RFC 5652 CMS SignedData, SHA-256 + RSA-4096) using the regulator-li-signing key in HSM. Store package.zip.enc.p7s.
  5. SFTP transfer — connect to ATRA drop-box via SSH-2 with fixed host-key pin (ssh-rsa in known_hosts.pinned). Key exchange prefers curve25519-sha256; MACs hmac-sha2-256-etm@openssh.com. Upload package.zip.enc, package.zip.enc.p7s, manifest.json, cover.pdf.
  6. Receipt confirmation — ATRA's drop-box writes a receipt.txt acknowledging receipt; poll every 60 s for up to 4 h. On receipt: UPDATE regulator.li_requests SET delivery_sftp_receipt_at = now() and emit regulator.li.delivered.v1.
  7. On SFTP failure — exponential backoff (5 retries, 2×/8×/32×/128×/512× seconds); persistent failure → FAILURE_MODES FM-06 runbook.

UC-05 — IngestComplaint

Trigger: POST /v1/regulator/complaints from an ATRA complaints-handler role.

Steps:

  1. RBAC gateregulator-read OR higher.
  2. Schema validation — E.164 citizenMsisdn, enum complaintType, summary ≤ 4000 chars, receivedAt ≤ now().
  3. PII encryption — encrypt full citizen_msisdn with tenant-scoped KEK (transit/regulator-complaints); store ciphertext in citizen_msisdn_ciphertext; store masked form (+CCNNN***) in queryable column.
  4. Compute SLAslaDueAt = receivedAt + 5 business days (Saturday-Thursday working week for AF).
  5. Optional AI triage — if FEATURE_COMPLAINT_TRIAGE_AI=true, invoke local classifier (see AI_INTEGRATION); write triage_ai_category best-guess + confidence.
  6. Persist + outboxregulator.complaints row (state RECEIVED); outbox regulator.complaint.received.v1.
  7. Workbench signaladmin-dashboard SSE 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:

  1. RBAC gateregulator-read or higher; scheduled jobs run under platform.regulator.scheduler.
  2. Create ReportJob with status = PENDING. Return reportJobId immediately.
  3. Job dispatcher picks it up (BullMQ-style backed by Redis; worker pool = 4):
    • DAILY_CDR_STATUS → read-through cdr-mediation-service /v1/internal/cdr/status (last 90 days); render HTML → PDF
    • MONTHLY_COMPLIANCE_SUMMARY → aggregate compliance-engine, consent-ledger-service, sender-id-registry-service, firewall-service via their internal REST/gRPC
    • AD_HOC → filters drive which upstream endpoints are queried
  4. On upstream partial failure — write a WARNINGS section in the report identifying which slices are missing; do not silently omit (regulator-trust preserving).
  5. Sign the PDF — see UC-07.
  6. Store + outboxs3://ghasi-regulator/reports/{yyyy}/{mm}/{reportJobId}.pdf[.p7s]; outbox regulator.report.submitted.v1 (for downstream analytics and SIEM).

UC-07 — SignReportPdf (HSM)

Steps:

  1. HSM handle — open a PKCS#11 session to SoftHSM (dev) or the network HSM (prod); login with CKU_USER via token PIN from Vault KV.
  2. SignC_SignInit with CKM_SHA256_RSA_PKCS_PSS, 4096-bit key regulator-reports-signing; C_Sign over the SHA-256 digest of the PDF bytes.
  3. PKCS#7 envelope — wrap signature in CMS SignedData (RFC 5652) with detached content; include the signing cert chain.
  4. Store<pdf>.p7s alongside the PDF.
  5. 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:

  1. 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.
  2. Destination dispatch:
    • SPLUNK_HECPOST https://splunk.atra.af:8088/services/collector with Authorization: Splunk {hec_token}; 2xx is ACK.
    • LOGSTASH_HTTPPOST https://logstash.atra.af/ingest with mTLS client cert; 2xx is ACK.
    • QRADAR_SYSLOG → TLS-wrapped syslog over :6514; rely on syslog-relay-ACK (RFC 5425 TLS transport).
  3. Write SiemDeliveryLog — status RETRYING until ACK, then ACKED.
  4. NATS ACK — only after all enabled destinations ACK. Destination-scoped DLQ on repeated failure keeps other destinations unblocked.
  5. Backpressure → disk-WAL — when NATS num_pending for this consumer exceeds SIEM_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)
  6. Recovery — when lag < 5 min and disk-WAL drained, revert to direct mode.
  7. Emit regulator.siem.forwarded.v1 once per destination ACK.

Budget: Formatter 1 ms; network P95 ≤ 500 ms per destination.


UC-09 — CollectEvidence (scheduled, hourly)

Trigger: Cron 0 * * * * (Asia/Kabul).

Steps:

  1. For each AttestationControl with evidence_type in AUTO_*:
    • AUTO_SBOM → read owner_service's /v1/internal/sbom endpoint; SBOM in CycloneDX format
    • AUTO_CI_RESULT → query GitHub API for latest main-branch CI run
    • AUTO_ACCESS_REVIEW → read auth-service /v1/internal/access-reviews?ownerService=X
    • AUTO_IMAGE_SIGNATURE → verify cosign signatures on running images via cluster admission controller's audit log
  2. Hash + store — compute SHA-256; write to s3://ghasi-regulator/evidence/{framework}/{controlId}/{hash}.{ext}.
  3. Update AttestationEvidence — upsert with status = CURRENT, last_review_at = now().
  4. Stale detection — any row where last_review_at + 60 days < now() is flipped to STALE; alert.
  5. Emit regulator.attestation.evidence.collected.v1.

UC-10 — AuditorLogin (time-boxed)

Trigger: mTLS handshake on https://auditor.ghasi.af/.

Steps:

  1. Certificate verified against auditor_ca_trust_store (separate trust anchor from national PKI).
  2. Subject-DN lookup in regulator.auditor_access — must have a granted row with accessExpiresAt > now().
  3. Session issued scoped to grantedFrameworks; all reads audit-logged with auditorId, frameworkAccessed, resourceRef.
  4. On accessExpiresAt pass, a background sweep invalidates all sessions for that auditor and flips status to EXPIRED.

UC-11 — GenerateAttestationBundle (annual)

Trigger: POST /v1/regulator/attestations/bundle?framework=ISO27001&year=2026 from platform.compliance.admin OR scheduled annual cron.

Steps:

  1. Collect — fetch all CURRENT evidence rows for (framework, year); re-verify each file hash against S3 ETag/SHA-256.
  2. Build control matrix — CSV + PDF mapping each controlId → evidence files + review timestamps + owner.
  3. Build exec summary — compliance-scorecard dashboard snapshot at year-end.
  4. Zipbundle.zip containing control-matrix.csv, control-matrix.pdf, exec-summary.pdf, evidence/ directory.
  5. Sign — PKCS#7 over bundle.zip with attestation-bundle-signing key (separate from report-signing key).
  6. Stores3://ghasi-regulator/bundles/{framework}/{year}/bundle.zip[.p7s] with permanent object-lock.
  7. 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:

  1. Pull fresh CRL from each trusted issuer.
  2. OCSP-query each active cert's status.
  3. On revocation: flip status → REVOKED, invalidate all live sessions for that userId, emit regulator.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

PathP95 budgetP99 ceiling
Regulator login (handshake → session)750 ms1500 ms
LI submit (excluding warrant upload)500 ms1000 ms
LI transition100 ms300 ms
Complaint ingest300 ms800 ms
Report listing200 ms500 ms
Report download (pre-signed URL issue)150 ms400 ms
Attestation catalog read200 ms500 ms
SIEM forward per destination500 ms1500 ms
Evidence collection cron (per control)10 s30 s
Attestation bundle generation (full)10 min30 min

4. Cross-references