system-prompts-and-models-o.../dealix/auto_client_acquisition/public_launch/pdpl_compliance.py
Sami Assiri b88a7c845f feat(public-launch): Layer 13 gate, PDPL, brand moat, docs and tests
- Add public_launch ACA package (gate, pilot_tracker, pdpl, brand_moat)
- Register GET/POST /api/v1/public-launch/* router in api/main
- Add 18 unit tests, MASTER_STRATEGIC_PLAN, PUBLIC_LAUNCH_READINESS
- Refresh POST_MERGE_VERIFICATION snapshot; vendor patch for git apply

Verified: 797 passed (6 skipped), ROUTE_CHECK_OK, SMOKE_INPROCESS_OK
2026-05-01 23:32:36 +03:00

177 lines
5.9 KiB
Python

"""PDPL Compliance — final readiness check before Public Launch.
Saudi Personal Data Protection Law (PDPL) compliance check covering:
- Data residency
- Opt-in audit per channel
- Breach notification readiness (72-hour rule)
- DPA template availability
- Privacy policy bilingual publication
- SDAIA registration status (if applicable)
- Data retention policy enforcement
Deterministic — no I/O, no LLM. State is passed in as dict.
"""
from __future__ import annotations
from dataclasses import dataclass, asdict
from typing import Any, Mapping
@dataclass(frozen=True)
class PDPLCheck:
key: str
name_ar: str
description_ar: str
severity: str # "critical" | "high" | "medium"
PDPL_CHECKS: tuple[PDPLCheck, ...] = (
PDPLCheck(
key="data_residency_saudi",
name_ar="إقامة البيانات في السعودية",
description_ar="بيانات العملاء مخزنة في region سعودي أو لدى شريك سعودي معتمد",
severity="critical",
),
PDPLCheck(
key="whatsapp_opt_in_audit",
name_ar="تدقيق opt-in لـ WhatsApp",
description_ar="كل رقم واتساب فيه opt-in موثّق قبل الإرسال",
severity="critical",
),
PDPLCheck(
key="email_opt_in_audit",
name_ar="تدقيق opt-in لـ Email",
description_ar="كل بريد إلكتروني فيه opt-in موثّق أو جاء عبر website form",
severity="high",
),
PDPLCheck(
key="breach_notification_72h_ready",
name_ar="جاهزية إبلاغ التسريب خلال 72 ساعة",
description_ar="عند تسريب بيانات: إبلاغ SDAIA + العملاء المتأثرين خلال 72 ساعة",
severity="critical",
),
PDPLCheck(
key="dpa_template_published",
name_ar="نشر نموذج DPA",
description_ar="نموذج Data Processing Agreement عربي/إنجليزي متاح للعملاء",
severity="high",
),
PDPLCheck(
key="privacy_policy_bilingual",
name_ar="سياسة الخصوصية ثنائية اللغة",
description_ar="Privacy Policy منشورة بالعربية والإنجليزية على dealix.me",
severity="critical",
),
PDPLCheck(
key="data_retention_policy",
name_ar="سياسة احتفاظ البيانات",
description_ar="بيانات الـ leads غير المتفاعلين تُحذف بعد 90 يوم",
severity="high",
),
PDPLCheck(
key="trace_redaction_active",
name_ar="إخفاء PII من traces",
description_ar="Trace Redactor مفعّل ويغطي email/phone/national_id/passport",
severity="critical",
),
PDPLCheck(
key="action_ledger_audit",
name_ar="سجل الأفعال (Action Ledger)",
description_ar="كل external action مسجّل مع who/what/when/approval_id",
severity="high",
),
PDPLCheck(
key="consent_revocation_path",
name_ar="مسار إلغاء الموافقة",
description_ar="العميل يقدر يلغي opt-in عبر link/email/WhatsApp بدون احتكاك",
severity="medium",
),
)
@dataclass
class PDPLReport:
overall_status: str # "compliant" | "needs_fixes" | "non_compliant"
score_passed: int
score_total: int
critical_failures: list[Mapping[str, Any]]
high_failures: list[Mapping[str, Any]]
medium_failures: list[Mapping[str, Any]]
check_results: list[Mapping[str, Any]]
summary_ar: str
next_actions_ar: list[str]
def to_dict(self) -> dict[str, Any]:
return asdict(self)
def compute_pdpl_compliance(state: Mapping[str, Any]) -> PDPLReport:
"""Evaluate PDPL compliance.
Args:
state: dict mapping check key → bool (True = compliant).
Keys not in state default to False (non-compliant).
Returns:
PDPLReport with overall status, failures by severity, next actions.
"""
results: list[dict[str, Any]] = []
critical_failures: list[dict[str, Any]] = []
high_failures: list[dict[str, Any]] = []
medium_failures: list[dict[str, Any]] = []
actions: list[str] = []
passed = 0
for check in PDPL_CHECKS:
ok = bool(state.get(check.key, False))
result = {
"key": check.key,
"name_ar": check.name_ar,
"passed": ok,
"severity": check.severity,
"description_ar": check.description_ar,
}
results.append(result)
if ok:
passed += 1
else:
if check.severity == "critical":
critical_failures.append(result)
actions.append(f"🛑 [CRITICAL] {check.name_ar}{check.description_ar}")
elif check.severity == "high":
high_failures.append(result)
actions.append(f"⚠️ [HIGH] {check.name_ar}")
else:
medium_failures.append(result)
total = len(PDPL_CHECKS)
if critical_failures:
status = "non_compliant"
summary = (
f"🛑 NON-COMPLIANT — {len(critical_failures)} مشاكل critical. "
"لا تنتقل لـ Public Launch قبل إصلاحها."
)
elif high_failures:
status = "needs_fixes"
summary = (
f"⚠️ NEEDS FIXES — {len(high_failures)} مشاكل high. "
f"PDPL compliance score: {passed}/{total}."
)
else:
status = "compliant"
summary = f"✅ COMPLIANT — كل {total} فحوصات PDPL متحققة."
return PDPLReport(
overall_status=status,
score_passed=passed,
score_total=total,
critical_failures=critical_failures,
high_failures=high_failures,
medium_failures=medium_failures,
check_results=results,
summary_ar=summary,
next_actions_ar=actions,
)