diff --git a/dealix/api/main.py b/dealix/api/main.py
index be9ad39d..383cb6a6 100644
--- a/dealix/api/main.py
+++ b/dealix/api/main.py
@@ -23,6 +23,7 @@ from api.routers import (
business,
command_center,
connector_catalog,
+ customer_ops,
customer_success,
data,
dominance,
@@ -178,6 +179,7 @@ def create_app() -> FastAPI:
app.include_router(revenue_launch.router)
app.include_router(autonomous_service_operator.router)
app.include_router(revenue_company_os.router)
+ app.include_router(customer_ops.router)
app.include_router(public.router)
app.include_router(admin.router)
diff --git a/dealix/api/routers/customer_ops.py b/dealix/api/routers/customer_ops.py
new file mode 100644
index 00000000..d3011b17
--- /dev/null
+++ b/dealix/api/routers/customer_ops.py
@@ -0,0 +1,208 @@
+"""Customer Ops router — onboarding + connectors + support + SLA + incidents."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import APIRouter, Body
+
+from auto_client_acquisition.customer_ops import (
+ SUPPORT_PRIORITIES,
+ SUPPORTED_CONNECTORS,
+ build_at_risk_alert,
+ build_connector_setup_summary,
+ build_customer_success_plan,
+ build_first_response_template,
+ build_incident_response_plan,
+ build_onboarding_checklist,
+ build_sla_health_report,
+ build_weekly_check_in,
+ classify_sla_breach,
+ classify_ticket_priority,
+ record_sla_event,
+ route_ticket,
+ triage_incident,
+ update_connector_status,
+ update_onboarding_step,
+)
+
+router = APIRouter(prefix="/api/v1/customer-ops", tags=["customer-ops"])
+
+
+# ── Onboarding ───────────────────────────────────────────────
+@router.post("/onboarding/checklist")
+async def onboarding_checklist(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_onboarding_checklist(
+ customer_id=payload.get("customer_id", ""),
+ company_name=payload.get("company_name", ""),
+ bundle_id=payload.get("bundle_id"),
+ )
+
+
+@router.post("/onboarding/update-step")
+async def onboarding_update_step(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return update_onboarding_step(
+ payload.get("checklist") or {},
+ step_id=payload.get("step_id", ""),
+ completed=bool(payload.get("completed", True)),
+ notes=payload.get("notes", ""),
+ )
+
+
+@router.get("/onboarding/checklist/demo")
+async def onboarding_checklist_demo() -> dict[str, Any]:
+ return build_onboarding_checklist(
+ customer_id="demo", company_name="شركة نمو للتدريب",
+ bundle_id="growth_starter",
+ )
+
+
+# ── Connectors ───────────────────────────────────────────────
+@router.get("/connectors/catalog")
+async def connectors_catalog() -> dict[str, Any]:
+ return {
+ "total": len(SUPPORTED_CONNECTORS),
+ "connectors": [dict(c) for c in SUPPORTED_CONNECTORS],
+ }
+
+
+@router.post("/connectors/summary")
+async def connectors_summary(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_connector_setup_summary(
+ customer_id=payload.get("customer_id", ""),
+ statuses=payload.get("statuses"),
+ )
+
+
+@router.post("/connectors/update")
+async def connectors_update(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ statuses = payload.get("statuses") or {}
+ try:
+ return {"statuses": update_connector_status(
+ statuses,
+ connector_key=payload.get("connector_key", ""),
+ state=payload.get("state", "not_started"),
+ notes=payload.get("notes", ""),
+ )}
+ except ValueError as exc:
+ return {"error": str(exc)}
+
+
+@router.get("/connectors/demo")
+async def connectors_demo() -> dict[str, Any]:
+ return build_connector_setup_summary(
+ customer_id="demo",
+ statuses={
+ "gmail": {"state": "connected_draft_only"},
+ "google_calendar": {"state": "connected_draft_only"},
+ "moyasar": {"state": "configuring"},
+ "whatsapp_cloud": {"state": "not_started"},
+ },
+ )
+
+
+# ── Support ──────────────────────────────────────────────────
+@router.get("/support/priorities")
+async def support_priorities() -> dict[str, Any]:
+ return {"priorities": [dict(p) for p in SUPPORT_PRIORITIES]}
+
+
+@router.post("/support/classify")
+async def support_classify(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return classify_ticket_priority(payload.get("text", ""))
+
+
+@router.post("/support/route")
+async def support_route(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return route_ticket(
+ text=payload.get("text", ""),
+ customer_id=payload.get("customer_id", ""),
+ contact_email=payload.get("contact_email", ""),
+ )
+
+
+@router.get("/support/first-response/{priority}")
+async def support_first_response(priority: str) -> dict[str, Any]:
+ return build_first_response_template(priority)
+
+
+# ── SLA ──────────────────────────────────────────────────────
+@router.post("/sla/event")
+async def sla_event(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ try:
+ return record_sla_event(
+ ticket_id=payload.get("ticket_id", ""),
+ priority=payload.get("priority", "P3"),
+ event=payload.get("event", "opened"),
+ )
+ except ValueError as exc:
+ return {"error": str(exc)}
+
+
+@router.post("/sla/classify-breach")
+async def sla_classify_breach(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return classify_sla_breach(
+ priority=payload.get("priority", "P3"),
+ minutes_to_first_response=payload.get("minutes_to_first_response"),
+ hours_to_resolve=payload.get("hours_to_resolve"),
+ )
+
+
+@router.post("/sla/health-report")
+async def sla_health_report(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_sla_health_report(tickets=payload.get("tickets") or [])
+
+
+@router.get("/sla/health-report/demo")
+async def sla_health_report_demo() -> dict[str, Any]:
+ return build_sla_health_report(tickets=[
+ {"priority": "P0", "first_response_min": 12, "resolution_hours": 2.5},
+ {"priority": "P1", "first_response_min": 90, "resolution_hours": 18},
+ {"priority": "P2", "first_response_min": 600, "resolution_hours": 70},
+ {"priority": "P3", "first_response_min": 1200, "resolution_hours": 100},
+ ])
+
+
+# ── Incidents ────────────────────────────────────────────────
+@router.post("/incidents/triage")
+async def incidents_triage(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return triage_incident(
+ title=payload.get("title", ""),
+ description=payload.get("description", ""),
+ affected_customers=int(payload.get("affected_customers", 1)),
+ has_data_leak=bool(payload.get("has_data_leak", False)),
+ has_unauthorized_send=bool(payload.get("has_unauthorized_send", False)),
+ )
+
+
+@router.get("/incidents/response-plan/{severity}")
+async def incidents_response_plan(severity: str) -> dict[str, Any]:
+ return build_incident_response_plan(severity=severity)
+
+
+# ── Customer Success ─────────────────────────────────────────
+@router.post("/cs/weekly-check-in")
+async def cs_weekly_check_in(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_weekly_check_in(
+ customer_id=payload.get("customer_id", ""),
+ company_name=payload.get("company_name", ""),
+ metrics=payload.get("metrics"),
+ )
+
+
+@router.post("/cs/at-risk-alert")
+async def cs_at_risk_alert(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_at_risk_alert(
+ customer_id=payload.get("customer_id", ""),
+ days_inactive=int(payload.get("days_inactive", 0)),
+ drafts_pending=int(payload.get("drafts_pending", 0)),
+ last_proof_pack_days_ago=int(payload.get("last_proof_pack_days_ago", 0)),
+ )
+
+
+@router.post("/cs/success-plan")
+async def cs_success_plan(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_customer_success_plan(
+ customer_id=payload.get("customer_id", ""),
+ bundle_id=payload.get("bundle_id", "growth_starter"),
+ )
diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py b/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py
index 15255081..8cf4c0a4 100644
--- a/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py
+++ b/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py
@@ -22,6 +22,14 @@ from .client_mode import (
build_client_dashboard,
build_client_session_summary,
)
+from .self_growth_mode import (
+ build_operator_self_growth_brief,
+)
+from .service_delivery_mode import (
+ build_post_delivery_handoff,
+ build_service_delivery_brief,
+ build_sla_status_for_delivery,
+)
from .conversation_router import (
INTENT_TO_HANDLER,
handle_message,
@@ -122,4 +130,10 @@ __all__ = [
# agency_mode
"add_agency_client", "build_agency_dashboard",
"build_co_branded_proof_pack", "list_agency_revenue_share",
+ # self_growth_mode
+ "build_operator_self_growth_brief",
+ # service_delivery_mode
+ "build_post_delivery_handoff",
+ "build_service_delivery_brief",
+ "build_sla_status_for_delivery",
]
diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py
new file mode 100644
index 00000000..82a9110c
--- /dev/null
+++ b/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py
@@ -0,0 +1,55 @@
+"""Self-Growth Mode — Dealix uses its own OS to grow.
+
+Re-exports + extends targeting_os.self_growth_mode with operator-tier wiring.
+"""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.targeting_os.self_growth_mode import (
+ DEALIX_ICP_FOCUSES,
+ build_dealix_self_growth_plan,
+ build_free_service_offer,
+ build_self_growth_daily_brief,
+ build_weekly_learning_report,
+ recommend_dealix_targets,
+)
+
+
+def build_operator_self_growth_brief(
+ *,
+ include_outreach_hint: bool = True,
+) -> dict[str, Any]:
+ """
+ Operator-tier wrapper around the self-growth daily brief.
+
+ Layers in approval-first reminders + reminders to never auto-send.
+ """
+ base = build_self_growth_daily_brief()
+ out = dict(base)
+ out["operator_reminders_ar"] = [
+ "لا cold WhatsApp — حتى داخل Dealix نفسه.",
+ "كل رسالة draft تحتاج اعتمادك قبل الإرسال.",
+ "لا scraping LinkedIn — استخدم Lead Forms أو manual research.",
+ "كل تواصل يدخل Action Ledger.",
+ ]
+ if include_outreach_hint:
+ out["next_action_ar"] = (
+ "اعتمد 3 رسائل اليوم فقط — جودة قبل كمية. "
+ "Pilot صغير ناجح > 50 رسالة بدون رد."
+ )
+ out["approval_required"] = True
+ out["live_send_allowed"] = False
+ return out
+
+
+__all__ = [
+ "DEALIX_ICP_FOCUSES",
+ "build_dealix_self_growth_plan",
+ "build_free_service_offer",
+ "build_operator_self_growth_brief",
+ "build_self_growth_daily_brief",
+ "build_weekly_learning_report",
+ "recommend_dealix_targets",
+]
diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py
new file mode 100644
index 00000000..9ee2239d
--- /dev/null
+++ b/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py
@@ -0,0 +1,108 @@
+"""Service Delivery Mode — runs client services + tracks SLA + generates Proof.
+
+Production wrapper around service_orchestrator + revenue_launch.pilot_delivery
++ customer_ops.sla_tracker.
+"""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.customer_ops import (
+ build_sla_health_report,
+ classify_sla_breach,
+)
+from auto_client_acquisition.revenue_launch import (
+ build_24h_delivery_plan,
+ build_first_10_opportunities_delivery,
+ build_growth_diagnostic_delivery,
+ build_list_intelligence_delivery,
+)
+from auto_client_acquisition.service_tower import (
+ build_service_workflow,
+ get_service,
+)
+
+
+def build_service_delivery_brief(
+ *,
+ customer_id: str = "",
+ service_id: str = "",
+ intake: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build the day-one delivery brief for a service."""
+ s = get_service(service_id)
+ if s is None:
+ return {"error": f"unknown service: {service_id}"}
+
+ delivery_template_by_service: dict[str, Any] = {
+ "first_10_opportunities_sprint":
+ build_first_10_opportunities_delivery(intake or {}),
+ "list_intelligence":
+ build_list_intelligence_delivery(intake or {}),
+ "free_growth_diagnostic":
+ build_growth_diagnostic_delivery(intake or {}),
+ }
+
+ return {
+ "mode": "service_delivery",
+ "customer_id": customer_id,
+ "service_id": service_id,
+ "service_name_ar": s.name_ar,
+ "intake_received": bool(intake),
+ "workflow": build_service_workflow(service_id),
+ "delivery_template": delivery_template_by_service.get(
+ service_id, build_24h_delivery_plan(service_id),
+ ),
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_sla_status_for_delivery(
+ *,
+ customer_id: str = "",
+ open_tickets: list[dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """Compute SLA health for a customer's open delivery tickets."""
+ health = build_sla_health_report(tickets=open_tickets)
+ breaches: list[dict[str, Any]] = []
+ for t in (open_tickets or []):
+ b = classify_sla_breach(
+ priority=str(t.get("priority", "P3")),
+ minutes_to_first_response=t.get("first_response_min"),
+ hours_to_resolve=t.get("resolution_hours"),
+ )
+ if b["breached"]:
+ breaches.append({**t, "breach": b})
+ return {
+ "customer_id": customer_id,
+ "health": health,
+ "breaches": breaches,
+ "approval_required": True,
+ }
+
+
+def build_post_delivery_handoff(
+ *,
+ customer_id: str = "",
+ service_id: str = "",
+ delivered_metrics: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build the post-delivery handoff (Arabic) → Customer Success cadence."""
+ metrics = delivered_metrics or {}
+ return {
+ "mode": "service_delivery",
+ "customer_id": customer_id,
+ "service_id": service_id,
+ "delivered_metrics": dict(metrics),
+ "handoff_steps_ar": [
+ "تسليم Proof Pack النهائي للعميل + اعتماده.",
+ "حجز جلسة مراجعة 30 دقيقة.",
+ "تفعيل Customer Success cadence (weekly check-ins).",
+ "اقتراح الترقية المنطقية بناءً على النتائج.",
+ "تحديث Action Graph + Revenue Work Units.",
+ ],
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
diff --git a/dealix/auto_client_acquisition/customer_ops/__init__.py b/dealix/auto_client_acquisition/customer_ops/__init__.py
new file mode 100644
index 00000000..311847ae
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/__init__.py
@@ -0,0 +1,78 @@
+"""Customer Ops — onboarding + connector setup + support SLA + incidents.
+
+Closes the gap between "great product" and "great customer experience":
+ - onboarding_checklist: 8-step Pilot onboarding
+ - connector_setup_status: per-connector readiness
+ - support_ticket_router: P0–P3 categorization + routing
+ - sla_tracker: time-to-first-response, MTTR, weekly health
+ - customer_success_cadence: weekly check-in cadence + risk flags
+ - incident_router: triage P0/P1 incidents with audit
+"""
+
+from __future__ import annotations
+
+from .connector_setup_status import (
+ SUPPORTED_CONNECTORS,
+ build_connector_setup_summary,
+ get_connector_status,
+ update_connector_status,
+)
+from .customer_success_cadence import (
+ CADENCE_TYPES,
+ build_at_risk_alert,
+ build_customer_success_plan,
+ build_weekly_check_in,
+)
+from .incident_router import (
+ INCIDENT_SEVERITIES,
+ build_incident_response_plan,
+ triage_incident,
+)
+from .onboarding_checklist import (
+ ONBOARDING_STEPS,
+ build_onboarding_checklist,
+ update_onboarding_step,
+)
+from .sla_tracker import (
+ SLA_TARGETS,
+ build_sla_health_report,
+ classify_sla_breach,
+ record_sla_event,
+)
+from .support_ticket_router import (
+ SUPPORT_PRIORITIES,
+ build_first_response_template,
+ classify_ticket_priority,
+ route_ticket,
+)
+
+__all__ = [
+ # connector_setup_status
+ "SUPPORTED_CONNECTORS",
+ "build_connector_setup_summary",
+ "get_connector_status",
+ "update_connector_status",
+ # customer_success_cadence
+ "CADENCE_TYPES",
+ "build_at_risk_alert",
+ "build_customer_success_plan",
+ "build_weekly_check_in",
+ # incident_router
+ "INCIDENT_SEVERITIES",
+ "build_incident_response_plan",
+ "triage_incident",
+ # onboarding_checklist
+ "ONBOARDING_STEPS",
+ "build_onboarding_checklist",
+ "update_onboarding_step",
+ # sla_tracker
+ "SLA_TARGETS",
+ "build_sla_health_report",
+ "classify_sla_breach",
+ "record_sla_event",
+ # support_ticket_router
+ "SUPPORT_PRIORITIES",
+ "build_first_response_template",
+ "classify_ticket_priority",
+ "route_ticket",
+]
diff --git a/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py b/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py
new file mode 100644
index 00000000..f74310fb
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py
@@ -0,0 +1,98 @@
+"""Connector setup status — per-customer readiness across all integrations."""
+
+from __future__ import annotations
+
+from typing import Any
+
+# 11 connectors Dealix supports during onboarding.
+SUPPORTED_CONNECTORS: tuple[dict[str, Any], ...] = (
+ {"key": "gmail", "label_ar": "Gmail", "default_mode": "draft_only",
+ "blocking": False, "phase": "phase_1"},
+ {"key": "google_calendar", "label_ar": "Google Calendar",
+ "default_mode": "draft_only", "blocking": False, "phase": "phase_1"},
+ {"key": "google_sheets", "label_ar": "Google Sheets",
+ "default_mode": "approved_execute", "blocking": False, "phase": "phase_1"},
+ {"key": "moyasar", "label_ar": "Moyasar (manual invoice)",
+ "default_mode": "manual", "blocking": False, "phase": "phase_1"},
+ {"key": "whatsapp_cloud", "label_ar": "WhatsApp Business",
+ "default_mode": "draft_only", "blocking": True, "phase": "phase_1"},
+ {"key": "website_forms", "label_ar": "Website Forms",
+ "default_mode": "approved_execute", "blocking": False, "phase": "phase_1"},
+ {"key": "linkedin_lead_forms", "label_ar": "LinkedIn Lead Gen Forms",
+ "default_mode": "ingest_only", "blocking": False, "phase": "phase_2"},
+ {"key": "google_business_profile", "label_ar": "Google Business Profile",
+ "default_mode": "draft_only", "blocking": False, "phase": "phase_2"},
+ {"key": "crm_generic", "label_ar": "CRM (HubSpot/Salesforce/Zoho/Close)",
+ "default_mode": "draft_only", "blocking": False, "phase": "phase_2"},
+ {"key": "google_meet", "label_ar": "Google Meet (transcripts)",
+ "default_mode": "ingest_only", "blocking": False, "phase": "phase_2"},
+ {"key": "instagram_graph", "label_ar": "Instagram (comments/DMs)",
+ "default_mode": "ingest_only", "blocking": False, "phase": "phase_3"},
+)
+
+
+def get_connector_status(connector_key: str) -> dict[str, Any]:
+ """Return the static description of a connector."""
+ c = next((dict(c) for c in SUPPORTED_CONNECTORS if c["key"] == connector_key), None)
+ if c is None:
+ return {"error": f"unknown connector: {connector_key}"}
+ return c
+
+
+def update_connector_status(
+ statuses: dict[str, dict[str, Any]],
+ *,
+ connector_key: str,
+ state: str,
+ notes: str = "",
+) -> dict[str, dict[str, Any]]:
+ """Update the live status of a connector for a customer."""
+ if state not in {"not_started", "configuring", "connected_draft_only",
+ "connected_approved_execute", "failed", "skipped"}:
+ raise ValueError(f"Unknown connector state: {state}")
+ statuses[connector_key] = {
+ "state": state,
+ "notes": notes[:200],
+ }
+ return statuses
+
+
+def build_connector_setup_summary(
+ *,
+ customer_id: str = "",
+ statuses: dict[str, dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """Build a connector setup summary for a customer."""
+ statuses = statuses or {}
+ connected = 0
+ blocking_missing: list[str] = []
+ by_state: dict[str, int] = {}
+
+ items: list[dict[str, Any]] = []
+ for c in SUPPORTED_CONNECTORS:
+ live = statuses.get(c["key"], {})
+ state = live.get("state", "not_started")
+ by_state[state] = by_state.get(state, 0) + 1
+ if state in ("connected_draft_only", "connected_approved_execute"):
+ connected += 1
+ if c["blocking"] and state not in (
+ "connected_draft_only", "connected_approved_execute",
+ ):
+ blocking_missing.append(c["key"])
+ items.append({**c, "state": state, "notes": live.get("notes", "")})
+
+ total = len(SUPPORTED_CONNECTORS)
+ pct = round(100 * connected / total, 1) if total else 0.0
+
+ return {
+ "customer_id": customer_id,
+ "total_connectors": total,
+ "connected_count": connected,
+ "connected_pct": pct,
+ "blocking_missing": blocking_missing,
+ "by_state": by_state,
+ "items": items,
+ "ready_for_first_service": (
+ len(blocking_missing) == 0 and connected >= 1
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py b/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py
new file mode 100644
index 00000000..c26404b3
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py
@@ -0,0 +1,146 @@
+"""Customer Success cadence — weekly check-ins + at-risk alerts."""
+
+from __future__ import annotations
+
+from typing import Any
+
+# Cadence types Dealix supports.
+CADENCE_TYPES: tuple[str, ...] = (
+ "weekly_check_in",
+ "monthly_proof_review",
+ "quarterly_business_review",
+ "at_risk_alert",
+ "renewal_30_day",
+ "renewal_7_day",
+)
+
+
+def build_weekly_check_in(
+ *,
+ customer_id: str = "",
+ company_name: str = "",
+ metrics: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build a weekly check-in agenda + Arabic talking points."""
+ m = metrics or {}
+ drafts = int(m.get("drafts_approved", 0))
+ replies = int(m.get("replies", 0))
+ meetings = int(m.get("meetings", 0))
+ risks = int(m.get("risks_blocked", 0))
+ pipeline = float(m.get("pipeline_sar", 0))
+
+ return {
+ "customer_id": customer_id,
+ "company_name": company_name,
+ "type": "weekly_check_in",
+ "agenda_ar": [
+ "مراجعة آخر Proof Pack (5 دقائق).",
+ "أبرز فرصة في الـ pipeline (5 دقائق).",
+ "أبرز خطر في القنوات (5 دقائق).",
+ "خطة الأسبوع القادم (5 دقائق).",
+ "أي مساعدة من فريقنا؟ (5 دقائق).",
+ ],
+ "talking_points_ar": [
+ f"اعتمدتم {drafts} رسالة هذا الأسبوع، ووصلكم {replies} رد.",
+ f"تم تجهيز {meetings} اجتماع.",
+ f"تم منع {risks} مخاطر تلقائياً.",
+ f"Pipeline متأثر بقيمة {pipeline:.0f} ريال.",
+ ],
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_at_risk_alert(
+ *,
+ customer_id: str = "",
+ days_inactive: int = 0,
+ drafts_pending: int = 0,
+ last_proof_pack_days_ago: int = 0,
+) -> dict[str, Any]:
+ """Build an at-risk alert when a customer shows churn signals."""
+ risk_score = 0
+ reasons: list[str] = []
+
+ if days_inactive >= 14:
+ risk_score += 40
+ reasons.append(f"العميل غير نشط منذ {days_inactive} يوم.")
+ elif days_inactive >= 7:
+ risk_score += 20
+ reasons.append(f"انخفاض النشاط منذ {days_inactive} يوم.")
+
+ if drafts_pending >= 10:
+ risk_score += 25
+ reasons.append(f"{drafts_pending} مسودة معلقة بدون اعتماد.")
+ elif drafts_pending >= 5:
+ risk_score += 10
+ reasons.append(f"تراكم {drafts_pending} مسودة بدون اعتماد.")
+
+ if last_proof_pack_days_ago >= 14:
+ risk_score += 30
+ reasons.append(
+ f"آخر Proof Pack قبل {last_proof_pack_days_ago} يوم — يتجاوز SLA."
+ )
+
+ risk_score = min(100, risk_score)
+ if risk_score >= 60:
+ severity = "high"
+ action_ar = "أرسل إيميل personal من المؤسس + احجز QBR هذا الأسبوع."
+ elif risk_score >= 30:
+ severity = "medium"
+ action_ar = "أرسل Proof Pack ملخص + اقترح ديمو لخدمة جديدة."
+ else:
+ severity = "low"
+ action_ar = "متابعة weekly check-in عادية."
+
+ return {
+ "customer_id": customer_id,
+ "type": "at_risk_alert",
+ "risk_score": risk_score,
+ "severity": severity,
+ "reasons_ar": reasons,
+ "recommended_action_ar": action_ar,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_customer_success_plan(
+ *,
+ customer_id: str = "",
+ bundle_id: str = "growth_starter",
+) -> dict[str, Any]:
+ """Build a 30-day customer success cadence plan."""
+ cadence_by_bundle = {
+ "growth_starter": [
+ "Day 1: kick-off call + intake.",
+ "Day 3: review first 3 opportunities + drafts.",
+ "Day 7: deliver Proof Pack v1.",
+ "Day 14: weekly check-in + upsell offer.",
+ "Day 30: monthly proof review + renewal/upgrade decision.",
+ ],
+ "executive_growth_os": [
+ "Day 1: onboarding + connect channels.",
+ "Day 7: first weekly Proof Pack.",
+ "Day 14: weekly check-in + Founder Shadow Board v1.",
+ "Day 21: monthly proof review.",
+ "Day 30: QBR + annual upgrade conversation.",
+ ],
+ "partnership_growth": [
+ "Day 1: partner ICP intake.",
+ "Day 5: 20 partners list + 10 outreach drafts.",
+ "Day 10: 5 partner meetings booked.",
+ "Day 14: weekly check-in.",
+ "Day 30: partner scorecard + revenue share setup.",
+ ],
+ }
+
+ return {
+ "customer_id": customer_id,
+ "bundle_id": bundle_id,
+ "cadence_ar": cadence_by_bundle.get(
+ bundle_id, cadence_by_bundle["growth_starter"],
+ ),
+ "default_cadence_type": "weekly_check_in",
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/customer_ops/incident_router.py b/dealix/auto_client_acquisition/customer_ops/incident_router.py
new file mode 100644
index 00000000..617ad24d
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/incident_router.py
@@ -0,0 +1,104 @@
+"""Incident router — triage P0/P1 incidents with audit + response plan."""
+
+from __future__ import annotations
+
+from typing import Any
+
+INCIDENT_SEVERITIES: tuple[dict[str, Any], ...] = (
+ {
+ "id": "SEV1",
+ "label_ar": "حرج جداً — تسريب أمان / إرسال خاطئ / تعطل كامل",
+ "first_action_minutes": 15,
+ "communication_cadence_minutes": 30,
+ },
+ {
+ "id": "SEV2",
+ "label_ar": "خدمة مهمة معطلة لعدد كبير من العملاء",
+ "first_action_minutes": 30,
+ "communication_cadence_minutes": 60,
+ },
+ {
+ "id": "SEV3",
+ "label_ar": "خدمة معطلة لعميل واحد أو degraded performance",
+ "first_action_minutes": 120,
+ "communication_cadence_minutes": 240,
+ },
+)
+
+
+def triage_incident(
+ *,
+ title: str,
+ description: str = "",
+ affected_customers: int = 1,
+ has_data_leak: bool = False,
+ has_unauthorized_send: bool = False,
+) -> dict[str, Any]:
+ """Triage an incident → severity + first actions + comms cadence."""
+ if has_data_leak or has_unauthorized_send:
+ sev = "SEV1"
+ reason_ar = (
+ "تسريب أمان أو إرسال غير معتمد — أعلى أولوية."
+ )
+ elif affected_customers >= 5:
+ sev = "SEV2"
+ reason_ar = f"عدد العملاء المتأثرين: {affected_customers} ≥ 5."
+ else:
+ sev = "SEV3"
+ reason_ar = "حدث محدود التأثير."
+
+ severity = next(
+ (dict(s) for s in INCIDENT_SEVERITIES if s["id"] == sev),
+ dict(INCIDENT_SEVERITIES[2]),
+ )
+
+ return {
+ "title": title[:120],
+ "description": description[:500],
+ "severity": sev,
+ "reason_ar": reason_ar,
+ "severity_details": severity,
+ "affected_customers": affected_customers,
+ "has_data_leak": has_data_leak,
+ "has_unauthorized_send": has_unauthorized_send,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_incident_response_plan(
+ *,
+ severity: str = "SEV3",
+) -> dict[str, Any]:
+ """Build the canonical incident response plan (Arabic)."""
+ common_steps = [
+ "1. تجميد الـ live actions على القناة المعنية فوراً.",
+ "2. إخطار المؤسس + on-call operator.",
+ "3. إنشاء incident channel مع timeline.",
+ "4. مراجعة Action Ledger للأفعال المرتبطة.",
+ "5. إذا تسريب: إخطار العملاء المتأثرين خلال 72 ساعة (PDPL).",
+ ]
+
+ if severity == "SEV1":
+ plan = common_steps + [
+ "6. تواصل مباشر مع المؤسس + خلية أزمة.",
+ "7. كتابة post-mortem خلال 24 ساعة.",
+ "8. مراجعة قانونية إن لزم.",
+ ]
+ elif severity == "SEV2":
+ plan = common_steps + [
+ "6. تحديث العملاء المتأثرين كل 60 دقيقة.",
+ "7. post-mortem خلال 48 ساعة.",
+ ]
+ else:
+ plan = common_steps + [
+ "6. تحديث العميل المتأثر مع كل خطوة.",
+ "7. post-mortem اختياري.",
+ ]
+
+ return {
+ "severity": severity,
+ "plan_ar": plan,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
diff --git a/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py b/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py
new file mode 100644
index 00000000..39950966
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py
@@ -0,0 +1,120 @@
+"""Onboarding checklist — the 8-step Pilot onboarding flow."""
+
+from __future__ import annotations
+
+from typing import Any
+
+ONBOARDING_STEPS: tuple[dict[str, Any], ...] = (
+ {
+ "id": "select_goal",
+ "label_ar": "اختيار الهدف الأساسي",
+ "input_required": "goal",
+ "minutes": 2,
+ "approval_required": False,
+ },
+ {
+ "id": "select_bundle",
+ "label_ar": "اختيار الباقة المناسبة",
+ "input_required": "bundle_id",
+ "minutes": 3,
+ "approval_required": True,
+ },
+ {
+ "id": "company_intake",
+ "label_ar": "بيانات الشركة",
+ "input_required": "company_profile",
+ "minutes": 5,
+ "approval_required": False,
+ },
+ {
+ "id": "connect_channels",
+ "label_ar": "ربط القنوات (Gmail/Calendar/Sheets — drafts فقط)",
+ "input_required": "channels_oauth",
+ "minutes": 8,
+ "approval_required": True,
+ },
+ {
+ "id": "upload_or_source",
+ "label_ar": "رفع قائمة أو ربط مصدر leads",
+ "input_required": "list_or_source",
+ "minutes": 5,
+ "approval_required": True,
+ },
+ {
+ "id": "risk_review",
+ "label_ar": "مراجعة المخاطر (PDPL + سمعة القناة)",
+ "input_required": None,
+ "minutes": 4,
+ "approval_required": True,
+ },
+ {
+ "id": "first_service_run",
+ "label_ar": "تشغيل أول خدمة (First 10 Opportunities أو List Intelligence)",
+ "input_required": None,
+ "minutes": 0, # async — Dealix runs it
+ "approval_required": True,
+ },
+ {
+ "id": "first_proof_pack",
+ "label_ar": "استلام أول Proof Pack",
+ "input_required": None,
+ "minutes": 0, # async
+ "approval_required": False,
+ },
+)
+
+
+def build_onboarding_checklist(
+ *,
+ customer_id: str = "",
+ company_name: str = "",
+ bundle_id: str | None = None,
+) -> dict[str, Any]:
+ """Build a fresh onboarding checklist for a new customer."""
+ return {
+ "customer_id": customer_id,
+ "company_name": company_name,
+ "bundle_id": bundle_id,
+ "total_steps": len(ONBOARDING_STEPS),
+ "current_step_id": ONBOARDING_STEPS[0]["id"],
+ "steps": [
+ {**dict(s), "completed": False} for s in ONBOARDING_STEPS
+ ],
+ "estimated_total_minutes": sum(int(s["minutes"]) for s in ONBOARDING_STEPS),
+ "live_send_allowed": False,
+ }
+
+
+def update_onboarding_step(
+ checklist: dict[str, Any],
+ *,
+ step_id: str,
+ completed: bool = True,
+ notes: str = "",
+) -> dict[str, Any]:
+ """Mark a step complete + advance current_step_id."""
+ steps = list(checklist.get("steps", []))
+ found = False
+ for i, s in enumerate(steps):
+ if s["id"] == step_id:
+ s["completed"] = bool(completed)
+ if notes:
+ s["notes"] = notes[:200]
+ steps[i] = s
+ found = True
+ # advance current_step_id
+ if completed and i + 1 < len(steps):
+ checklist["current_step_id"] = steps[i + 1]["id"]
+ elif completed and i + 1 == len(steps):
+ checklist["current_step_id"] = "done"
+ break
+
+ if not found:
+ return {**checklist, "error": f"unknown step: {step_id}"}
+
+ completed_count = sum(1 for s in steps if s["completed"])
+ checklist["steps"] = steps
+ checklist["progress_pct"] = round(
+ 100 * completed_count / max(1, len(steps)), 1,
+ )
+ return checklist
diff --git a/dealix/auto_client_acquisition/customer_ops/sla_tracker.py b/dealix/auto_client_acquisition/customer_ops/sla_tracker.py
new file mode 100644
index 00000000..36fdd6b5
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/sla_tracker.py
@@ -0,0 +1,132 @@
+"""SLA tracker — measure first-response, MTTR, weekly support health."""
+
+from __future__ import annotations
+
+import time
+from typing import Any
+
+# Default SLA targets per priority (minutes for first_response, hours for resolution).
+SLA_TARGETS: dict[str, dict[str, float]] = {
+ "P0": {"first_response_min": 30, "resolution_hours": 4},
+ "P1": {"first_response_min": 120, "resolution_hours": 24},
+ "P2": {"first_response_min": 480, "resolution_hours": 72},
+ "P3": {"first_response_min": 1440, "resolution_hours": 168},
+}
+
+
+def record_sla_event(
+ *,
+ ticket_id: str,
+ priority: str,
+ event: str,
+ log: list[dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """
+ Record an SLA event.
+
+ `event` = "opened" | "first_response" | "resolved" | "escalated".
+ """
+ if event not in {"opened", "first_response", "resolved", "escalated"}:
+ raise ValueError(f"Unknown SLA event: {event}")
+ entry: dict[str, Any] = {
+ "ticket_id": ticket_id,
+ "priority": priority,
+ "event": event,
+ "ts": time.time(),
+ }
+ if log is not None:
+ log.append(entry)
+ return entry
+
+
+def classify_sla_breach(
+ *,
+ priority: str,
+ minutes_to_first_response: float | None = None,
+ hours_to_resolve: float | None = None,
+) -> dict[str, Any]:
+ """Classify whether SLA was breached for a single ticket."""
+ target = SLA_TARGETS.get(priority, SLA_TARGETS["P3"])
+ breaches: list[str] = []
+
+ if (minutes_to_first_response is not None
+ and minutes_to_first_response > target["first_response_min"]):
+ breaches.append(
+ f"first_response: {minutes_to_first_response:.0f} > "
+ f"{target['first_response_min']} min"
+ )
+
+ if (hours_to_resolve is not None
+ and hours_to_resolve > target["resolution_hours"]):
+ breaches.append(
+ f"resolution: {hours_to_resolve:.1f}h > "
+ f"{target['resolution_hours']}h"
+ )
+
+ return {
+ "priority": priority,
+ "breached": bool(breaches),
+ "breaches": breaches,
+ }
+
+
+def build_sla_health_report(
+ *,
+ tickets: list[dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """Build a weekly SLA health report from a list of tickets."""
+ tickets = tickets or []
+ by_priority: dict[str, dict[str, Any]] = {}
+ total_tickets = len(tickets)
+ total_breached = 0
+
+ for t in tickets:
+ priority = str(t.get("priority", "P3"))
+ bucket = by_priority.setdefault(priority, {
+ "count": 0, "breaches": 0,
+ "total_first_response_min": 0.0,
+ "total_resolution_hours": 0.0,
+ "responded_count": 0, "resolved_count": 0,
+ })
+ bucket["count"] += 1
+ ftr = t.get("first_response_min")
+ ttr = t.get("resolution_hours")
+ b = classify_sla_breach(
+ priority=priority,
+ minutes_to_first_response=ftr,
+ hours_to_resolve=ttr,
+ )
+ if b["breached"]:
+ bucket["breaches"] += 1
+ total_breached += 1
+ if ftr is not None:
+ bucket["total_first_response_min"] += float(ftr)
+ bucket["responded_count"] += 1
+ if ttr is not None:
+ bucket["total_resolution_hours"] += float(ttr)
+ bucket["resolved_count"] += 1
+
+ # Compute averages.
+ for p, b in by_priority.items():
+ if b["responded_count"]:
+ b["avg_first_response_min"] = round(
+ b["total_first_response_min"] / b["responded_count"], 1,
+ )
+ if b["resolved_count"]:
+ b["avg_resolution_hours"] = round(
+ b["total_resolution_hours"] / b["resolved_count"], 2,
+ )
+
+ breach_rate = round(total_breached / total_tickets, 3) if total_tickets else 0.0
+
+ return {
+ "total_tickets": total_tickets,
+ "total_breached": total_breached,
+ "breach_rate": breach_rate,
+ "by_priority": by_priority,
+ "verdict": (
+ "healthy" if breach_rate < 0.10
+ else "watch" if breach_rate < 0.25
+ else "critical"
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py b/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py
new file mode 100644
index 00000000..1705abcf
--- /dev/null
+++ b/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py
@@ -0,0 +1,149 @@
+"""Support ticket router — P0–P3 categorization + routing + first-response template."""
+
+from __future__ import annotations
+
+import re
+from typing import Any
+
+# 4 priority tiers Dealix supports.
+SUPPORT_PRIORITIES: tuple[dict[str, Any], ...] = (
+ {
+ "id": "P0",
+ "label_ar": "حرج جداً — أمان / إرسال خاطئ / تعطل كامل",
+ "first_response_minutes": 30,
+ "resolution_target_hours": 4,
+ "escalation_owner": "founder",
+ },
+ {
+ "id": "P1",
+ "label_ar": "خدمة مهمة معطلة",
+ "first_response_minutes": 120,
+ "resolution_target_hours": 24,
+ "escalation_owner": "operator_oncall",
+ },
+ {
+ "id": "P2",
+ "label_ar": "Connector أو Proof Pack متأخر",
+ "first_response_minutes": 480, # 8h
+ "resolution_target_hours": 72,
+ "escalation_owner": "operator_oncall",
+ },
+ {
+ "id": "P3",
+ "label_ar": "سؤال عام / تحسين",
+ "first_response_minutes": 1440, # 24h
+ "resolution_target_hours": 168, # 1 week
+ "escalation_owner": "operator_team",
+ },
+)
+
+
+# Keyword → priority hints.
+_P0_KEYWORDS = (
+ "أمان", "تسريب", "إرسال خاطئ", "إرسال بدون موافقة",
+ "بدون موافقتي", "أرسل رسالة بدون", "أرسل بدون",
+ "secret", "leak", "data breach", "outage", "completely down",
+ "live charge", "charge بدون موافقة", "unauthorized",
+)
+_P1_KEYWORDS = (
+ "service down", "خدمة معطلة", "service failed",
+ "Pilot stopped", "Proof Pack مفقود",
+)
+_P2_KEYWORDS = (
+ "connector", "Gmail", "Calendar", "Sheets",
+ "WhatsApp setup", "Moyasar invoice",
+)
+
+
+def classify_ticket_priority(text: str) -> dict[str, Any]:
+ """
+ Classify a free-text support ticket → P0 / P1 / P2 / P3.
+
+ Deterministic keyword matching. Returns matched priority + reasoning.
+ """
+ text = (text or "").strip()
+ if not text:
+ return {"priority": "P3", "reason_ar": "لا يوجد نص — اعتبار افتراضي."}
+
+ text_lc = text.lower()
+ for kw in _P0_KEYWORDS:
+ if kw in text or kw.lower() in text_lc:
+ return {
+ "priority": "P0",
+ "matched_keyword": kw,
+ "reason_ar": f"كلمة حرجة مطابقة: {kw}",
+ }
+ for kw in _P1_KEYWORDS:
+ if kw in text or kw.lower() in text_lc:
+ return {
+ "priority": "P1",
+ "matched_keyword": kw,
+ "reason_ar": f"خدمة مهمة معطلة: {kw}",
+ }
+ for kw in _P2_KEYWORDS:
+ if kw in text or kw.lower() in text_lc:
+ return {
+ "priority": "P2",
+ "matched_keyword": kw,
+ "reason_ar": f"connector أو Proof Pack: {kw}",
+ }
+ return {"priority": "P3", "reason_ar": "افتراضي — سؤال أو تحسين."}
+
+
+def route_ticket(
+ *,
+ text: str,
+ customer_id: str = "",
+ contact_email: str = "",
+) -> dict[str, Any]:
+ """Classify + route a ticket to the right SLA + owner."""
+ classification = classify_ticket_priority(text)
+ priority = classification["priority"]
+
+ sla = next(
+ (dict(p) for p in SUPPORT_PRIORITIES if p["id"] == priority),
+ dict(SUPPORT_PRIORITIES[3]),
+ )
+
+ return {
+ "customer_id": customer_id,
+ "contact_email": contact_email,
+ "priority": priority,
+ "classification": classification,
+ "sla": sla,
+ "first_response_template": build_first_response_template(priority),
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_first_response_template(priority: str) -> dict[str, Any]:
+ """Build an Arabic first-response template per priority."""
+ if priority == "P0":
+ body = (
+ "وصلني بلاغك الآن. نتعامل معه كأولوية حرجة. "
+ "سأرد عليك خلال 30 دقيقة بتفاصيل ما حدث + الإجراءات المتخذة. "
+ "إذا اكتشفت أي إرسال غير معتمد أو تسريب بيانات، سأتواصل معك مباشرة."
+ )
+ elif priority == "P1":
+ body = (
+ "وصلني بلاغك. نتعامل معه كأولوية عالية. "
+ "سأرد بتفاصيل خلال ساعتين كحد أقصى."
+ )
+ elif priority == "P2":
+ body = (
+ "وصلني سؤالك حول الـ connector / Proof Pack. "
+ "سأتابع خلال 8 ساعات عمل وأرسل لك حل أو خطوات تالية."
+ )
+ else:
+ body = (
+ "شاكر لك على ملاحظتك. سأرد عليك خلال 24 ساعة عمل. "
+ "إذا الأمر عاجل، اكتب 'حرج' في رسالة جديدة وأرفعها للأولوية."
+ )
+
+ return {
+ "priority": priority,
+ "body_ar": body,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
diff --git a/dealix/docs/APPROVED_MARKET_MESSAGING.md b/dealix/docs/APPROVED_MARKET_MESSAGING.md
new file mode 100644
index 00000000..72f5f29d
--- /dev/null
+++ b/dealix/docs/APPROVED_MARKET_MESSAGING.md
@@ -0,0 +1,119 @@
+# Approved Market Messaging — رسائل التسويق المعتمدة
+
+> أي رسالة (LinkedIn post / X tweet / email / landing copy / WhatsApp DM) لازم تُختار من هنا أو متوافقة مع نبرة هذا الملف.
+
+---
+
+## Tagline (موحّد)
+
+> **Dealix — Saudi Revenue Execution OS**
+
+النسخة العربية:
+> **Dealix — نظام تشغيل الإيرادات السعودي**
+
+---
+
+## Elevator Pitch — 30 ثانية
+
+> Dealix يشغّل النمو للشركات السعودية والوكالات: يكتشف الفرص، يكتب الرسائل بالعربي، يطلب موافقتك قبل أي تواصل، ينسق الاجتماعات، ويثبت العائد بـ Proof Pack شهري — دون scraping ولا cold WhatsApp.
+
+---
+
+## 5 Headlines المعتمدة
+
+1. **حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack.**
+2. **شغّل Dealix لعملائك كـ Agency Growth OS.**
+3. **10 فرص في 10 دقائق + متابعة + Proof Pack — وأنت توافق قبل أي تواصل.**
+4. **Approval-first — لا scraping، لا cold WhatsApp، لا وعود مضمونة.**
+5. **Saudi Tone — رسائل عربية طبيعية، ليست ترجمة.**
+
+---
+
+## Positioning vs Competitors (نقاط مقارنة معتمدة)
+
+| المنافس | ما نقوله |
+|---------|---------|
+| HubSpot/Salesforce | "Dealix أخف، عربي، service-led، يجيب 'وش أسوي اليوم؟' للـ SMB." |
+| Gong | "Dealix يبدأ قبل المكالمة: targeting → رسالة → موافقة → اجتماع → proof." |
+| Clay | "Dealix يحول البيانات إلى خدمة مدفوعة + workflow + Proof Pack." |
+| WhatsApp tools | "Dealix يقرر هل التواصل آمن، يطلب موافقة، ويثبت العائد." |
+| Agencies | "Dealix يحول خدمات الوكالة إلى operating system قابل للتكرار." |
+| Generic AI agent | "Dealix لديه services + policies + approvals + proof + revenue work units." |
+
+---
+
+## Outreach Templates (4 segments)
+
+### وكالات تسويق B2B
+هلا [الاسم]، عندي Beta خاص للوكالات.
+Dealix يساعد الوكالة تطلع فرص لعملائها، تجهز رسائل عربية، تدير موافقات، وتطلع Proof Pack بعلامة الوكالة والعميل.
+أبحث عن وكالة واحدة نجرب معها Pilot مشترك على عميل حقيقي. يناسبك ديمو 15 دقيقة؟
+
+### تدريب / استشارات
+هلا [الاسم]، متابع توسعكم في برامج الشركات.
+Dealix يطلع لكم 10 فرص B2B خلال 7 أيام، يكتب الرسائل بالعربي، ويخلي صانع القرار يوافق قبل أي تواصل، وبعدها يعطي Proof Pack.
+Pilot بـ499 ريال أو مجاني مقابل case study. يناسبك ديمو 12 دقيقة؟
+
+### SaaS / تقنية صغيرة
+هلا [الاسم]، رأيت إصدار النسخة الجديدة من منتجكم — مبروك.
+نشتغل على مدير نمو عربي يطلع 10 فرص B2B عبر LinkedIn Lead Forms (لا scraping)، ويكتب الرسائل بالعربي.
+أبغى أجربه مع شركة SaaS سعودية واحدة. يناسبك ديمو 12 دقيقة؟
+
+### خدمات بقاعدة واتساب
+هلا [الاسم]، عندكم قاعدة عملاء واتساب نشطة، صحيح؟
+Dealix ينظف القائمة، يصنف الـ opt-in، يحظر cold WhatsApp تلقائياً، ويكتب رسائل عربية للحملات الآمنة + Proof Pack شهري.
+List Intelligence بـ499–1,500 ريال. يناسبك أعطيك تشخيص مجاني أولاً؟
+
+---
+
+## Social Posts المعتمدة
+
+### LinkedIn — Founder voice
+بعد عام من التطوير، Dealix جاهز كـ Saudi Revenue Execution OS.
+
+ليس CRM. ليس bot. ليس scraper.
+هو نظام يُشغّل النمو لشركتك أو وكالتك:
+• 10 فرص B2B + رسائل عربية + متابعة + Proof Pack
+• Approval-first في كل قناة
+• PDPL-aware: لا cold WhatsApp
+
+أفتح 5 Pilots بـ499 ريال هذا الأسبوع. مهتم؟
+
+### X/Twitter — أقصر
+Dealix Private Beta متاحة:
+- 10 فرص B2B + رسائل عربية خلال 7 أيام
+- Approval-first — لا cold WhatsApp ولا scraping
+- 499 ريال أو مجاني مقابل case study
+
+أبحث عن 5 شركات سعودية B2B لتجربة محدودة. DM للتفاصيل.
+
+---
+
+## ما يتوافق مع الـ tone
+
+- ✅ "نطلع لك" بدلاً من "نضمن لك".
+- ✅ "خلال 7 أيام" بدلاً من "خلال ساعات".
+- ✅ "بدون scraping" بدلاً من "نسحب كل البيانات".
+- ✅ "بموافقتك" بدلاً من "تلقائياً".
+- ✅ "Pilot 499 ريال" بدلاً من "تجربة مجانية مفتوحة".
+
+---
+
+## Slogan-bank (لاستخدام لاحق)
+
+- "نمو محسوب، لا وعود."
+- "كل قرار له Proof."
+- "تشغّل، نوافق، نتابع، نثبت."
+- "Saudi-first. Service-led. Approval-first."
+
+---
+
+## القاعدة
+
+كل copy يدخل الإنتاج يجب أن يمر:
+1. `safety_eval()` — لا "ضمان 100%" / "آخر فرصة".
+2. `saudi_tone_eval()` — score ≥50.
+3. مراجعة بشرية ضد `PROHIBITED_CLAIMS.md`.
+4. `quality_review.review_service_before_launch` للخدمة المرتبطة.
+
+أي copy يفشل → لا يخرج.
diff --git a/dealix/docs/CONNECTOR_SETUP_GUIDES.md b/dealix/docs/CONNECTOR_SETUP_GUIDES.md
new file mode 100644
index 00000000..7713da4c
--- /dev/null
+++ b/dealix/docs/CONNECTOR_SETUP_GUIDES.md
@@ -0,0 +1,204 @@
+# Connector Setup Guides
+
+> دليل مرجعي لربط كل قناة. **القاعدة:** `draft_only` افتراضياً. لا live action قبل env flag صريح + اعتماد بشري.
+
+---
+
+## 11 Connectors المدعومة
+
+| Key | Default Mode | Phase | Blocking للـ first service |
+|-----|--------------|------:|--------------------------|
+| gmail | draft_only | 1 | لا |
+| google_calendar | draft_only | 1 | لا |
+| google_sheets | approved_execute | 1 | لا |
+| moyasar | manual | 1 | لا |
+| whatsapp_cloud | draft_only | 1 | **نعم** |
+| website_forms | approved_execute | 1 | لا |
+| linkedin_lead_forms | ingest_only | 2 | لا |
+| google_business_profile | draft_only | 2 | لا |
+| crm_generic | draft_only | 2 | لا |
+| google_meet | ingest_only | 2 | لا |
+| instagram_graph | ingest_only | 3 | لا |
+
+---
+
+## 1. Gmail (drafts فقط افتراضياً)
+
+**Scopes المطلوبة:**
+- `gmail.compose` (لإنشاء drafts)
+- `gmail.modify` (لإدارة الـ labels — read-only labels فقط في Phase 1)
+
+**خطوات:**
+1. Google Cloud Console → Create OAuth client.
+2. أضف Dealix كـ application authorized.
+3. منح الصلاحيات على scopes أعلاه فقط.
+4. Dealix يستلم refresh_token + access_token.
+5. وضع التشغيل: `connected_draft_only`.
+
+**Live send:** يتطلب `GMAIL_ALLOW_LIVE_SEND=true` env + اعتماد بشري للرسالة.
+
+---
+
+## 2. Google Calendar (drafts فقط)
+
+**Scopes:**
+- `calendar.events` (drafts only)
+
+**خطوات:**
+1. نفس OAuth client من Gmail.
+2. أضف scope الـ calendar.
+3. Dealix يبني draft events.
+4. لا insert إلا بعد:
+ - `CALENDAR_ALLOW_LIVE_INSERT=true`
+ - اعتماد بشري لكل event.
+
+---
+
+## 3. Google Sheets (read + append بموافقة)
+
+**Scopes:**
+- `sheets.readonly` للقراءة
+- `sheets` للكتابة (append فقط)
+
+**خطوات:**
+1. نفس OAuth.
+2. حدد الـ Spreadsheet ID المستخدم لـ Pilot.
+3. Dealix يقرأ leads + يكتب Proof Pack.
+
+**Live append:** يحتاج اعتماد للحقول الحساسة. لا overwrite تلقائي.
+
+---
+
+## 4. Moyasar (manual فقط في Phase 1)
+
+**عملية الإعداد:**
+1. حساب Moyasar dashboard.
+2. **لا** إدخال API keys في Dealix.
+3. عند طلب دفع:
+ - Dealix يولّد invoice instructions (halalas-correct).
+ - المؤسس يدخل Moyasar manually + ينشئ invoice.
+ - يضع invoice URL في Dealix.
+4. تأكيد paid: يدوي عبر Moyasar dashboard ثم تحديث pipeline_tracker.
+
+**Phase 2:** ربط API + auto-invoice (مع env flag + audit).
+
+---
+
+## 5. WhatsApp Cloud (Blocking — drafts فقط)
+
+**هذا أهم connector.** بدون WhatsApp opt-in audit، Dealix لا يفعّل first service.
+
+**خطوات:**
+1. Meta Developer Account → WhatsApp Business Cloud.
+2. Phone number verification.
+3. Webhook URL = Dealix endpoint.
+4. **مهم:** opt-in audit أولاً عبر `whatsapp_strategy.requires_opt_in`.
+
+**Live send:** يتطلب:
+- `WHATSAPP_ALLOW_LIVE_SEND=true`
+- opt-in موثّق لكل رقم.
+- اعتماد بشري للرسالة.
+- موافقة العميل على template.
+
+---
+
+## 6. Website Forms (آمنة)
+
+**خطوات:**
+1. أضف form على موقع العميل.
+2. Webhook URL = Dealix endpoint.
+3. كل form submission يدخل كـ `form.submitted` event.
+4. Dealix يبني opportunity card تلقائياً.
+
+**Live send:** auto-acknowledgment email/WhatsApp مسموح بعد opt-in في الـ form.
+
+---
+
+## 7. LinkedIn Lead Gen Forms (Phase 2)
+
+**القاعدة:** lead forms فقط — **لا scraping** ولا auto-DM.
+
+**خطوات:**
+1. LinkedIn Campaign Manager → Lead Gen Form.
+2. Hidden fields: `campaign_name`, `sector`, `sales_owner`.
+3. Webhook إلى Dealix.
+4. كل lead → `linkedin_lead_form` source = safe.
+
+---
+
+## 8. Google Business Profile (Phase 2)
+
+**Scopes:**
+- `business.manage`
+- `reviews.read`
+
+**خطوات:**
+1. ربط GBP location.
+2. Dealix يقرأ reviews.
+3. يبني draft reply لكل review.
+4. **Live publish** يحتاج اعتماد + `GBP_ALLOW_LIVE_REPLY=true`.
+
+---
+
+## 9. CRM Generic (Phase 2)
+
+**Supported:** HubSpot, Salesforce, Zoho, Close.
+
+**خطوات:**
+1. OAuth حسب الـ CRM.
+2. Read-only في الأسبوع الأول.
+3. Write مع approval بعد الأسبوع الأول.
+4. لا overwrite owner تلقائي.
+
+---
+
+## 10. Google Meet (Phase 2)
+
+**Scopes:**
+- `meetings.space.readonly`
+- `conferenceRecords.transcripts.readonly`
+
+**خطوات:**
+1. OAuth.
+2. ingest transcripts بعد موافقة كل المشاركين.
+3. Dealix يستخرج objections + next steps + buyer intent.
+4. **لا** real-time listening في Phase 2.
+
+---
+
+## 11. Instagram Graph (Phase 3)
+
+**Phase 3 connector.** ingest only لـ comments + DMs + insights.
+
+---
+
+## Acceptance Criteria للـ connector
+
+كل connector يُعتبر مُعدّ بنجاح إذا:
+1. State = `connected_draft_only` أو `connected_approved_execute`.
+2. Test successful (Dealix قرأ شيء أو كتب draft).
+3. لا secrets exposed في الـ logs/traces.
+4. Audit entry في Action Ledger.
+
+---
+
+## Troubleshooting
+
+| مشكلة | الحل |
+|------|------|
+| OAuth callback failed | recheck redirect_uri في Google/Meta console |
+| WhatsApp Webhook 401 | تحقق من verify_token |
+| Moyasar invoice URL لم يصل | تحقق من dashboard email settings |
+| Sheets quota exceeded | خفض الـ append rate أو ربط second Sheet |
+| Calendar conflicts | استخدم `freebusy.query` قبل draft event |
+
+---
+
+## Endpoints
+
+```
+GET /api/v1/customer-ops/connectors/catalog
+POST /api/v1/customer-ops/connectors/summary
+POST /api/v1/customer-ops/connectors/update
+GET /api/v1/customer-ops/connectors/demo
+```
diff --git a/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md b/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md
new file mode 100644
index 00000000..5d8c7e47
--- /dev/null
+++ b/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md
@@ -0,0 +1,141 @@
+# Customer Success Playbook
+
+> **القاعدة:** كل عميل له cadence محسوب بحسب الـ bundle. كل تدهور في النشاط يولّد at-risk alert تلقائياً.
+
+---
+
+## Cadence Types
+
+```
+weekly_check_in
+monthly_proof_review
+quarterly_business_review
+at_risk_alert
+renewal_30_day
+renewal_7_day
+```
+
+---
+
+## Endpoints
+
+```
+POST /api/v1/customer-ops/cs/weekly-check-in
+POST /api/v1/customer-ops/cs/at-risk-alert
+POST /api/v1/customer-ops/cs/success-plan
+```
+
+---
+
+## Weekly Check-in Agenda (25 دقيقة)
+
+1. مراجعة آخر Proof Pack (5 دقائق).
+2. أبرز فرصة في الـ pipeline (5 دقائق).
+3. أبرز خطر في القنوات (5 دقائق).
+4. خطة الأسبوع القادم (5 دقائق).
+5. أي مساعدة من فريقنا؟ (5 دقائق).
+
+**Talking points** (تتولد آلياً من metrics):
+- "اعتمدتم {drafts_approved} رسالة هذا الأسبوع، ووصلكم {replies} رد."
+- "تم تجهيز {meetings} اجتماع."
+- "تم منع {risks_blocked} مخاطر تلقائياً."
+- "Pipeline متأثر بقيمة {pipeline_sar:.0f} ريال."
+
+---
+
+## At-Risk Detection
+
+النظام يحسب `risk_score` (0..100) من:
+
+| العامل | النقاط |
+|--------|-------:|
+| غير نشط ≥14 يوم | +40 |
+| غير نشط ≥7 يوم | +20 |
+| ≥10 drafts معلقة | +25 |
+| ≥5 drafts معلقة | +10 |
+| آخر Proof Pack ≥14 يوم | +30 |
+
+### Severity
+- ≥60 → high → إيميل مؤسس + QBR هذا الأسبوع.
+- ≥30 → medium → Proof Pack ملخص + ديمو خدمة جديدة.
+- <30 → low → weekly check-in عادية.
+
+---
+
+## Cadence per Bundle
+
+### Growth Starter
+- Day 1: kick-off call + intake.
+- Day 3: review first 3 opportunities + drafts.
+- Day 7: deliver Proof Pack v1.
+- Day 14: weekly check-in + upsell offer.
+- Day 30: monthly proof review + renewal/upgrade decision.
+
+### Executive Growth OS
+- Day 1: onboarding + connect channels.
+- Day 7: first weekly Proof Pack.
+- Day 14: weekly check-in + Founder Shadow Board v1.
+- Day 21: monthly proof review.
+- Day 30: QBR + annual upgrade conversation.
+
+### Partnership Growth
+- Day 1: partner ICP intake.
+- Day 5: 20 partners list + 10 outreach drafts.
+- Day 10: 5 partner meetings booked.
+- Day 14: weekly check-in.
+- Day 30: partner scorecard + revenue share setup.
+
+---
+
+## QBR (Quarterly Business Review)
+
+عند 90 يوم من اشتراك Growth OS:
+1. مراجعة 3 Proof Packs السابقة.
+2. حساب ROI: pipeline_x + closed_won_x.
+3. مقارنة مع benchmarks القطاع (من `growth_memory`).
+4. اقتراح تجارب الـ quarter القادم.
+5. مراجعة الـ pricing tier.
+
+---
+
+## Renewal Flow
+
+### 30-day-out
+- إرسال Proof Pack الشهري + رسالة ودية.
+- "نلاقيك في QBR لمراجعة العام القادم؟"
+
+### 7-day-out
+- إذا لم يجدّد: إيميل من المؤسس + خصم سنوي 15%.
+- إذا renewal at risk: at-risk alert تلقائي.
+
+### Renewal day
+- إرسال invoice + شكر.
+- بدء plan الـ quarter القادم.
+
+---
+
+## Health Score Formula
+
+```
+csat (0..10) × 5
++ pipeline_sar / 1000
++ meetings × 8
++ approval_rate × 50
+- days_inactive × 2
+- drafts_pending × 1
+```
+
+```
+≥75 = healthy
+50–74 = watch
+<50 = at-risk
+```
+
+---
+
+## ما لا يحدث في CS
+
+- لا "عرض ترقية" قبل تسليم أول Proof Pack.
+- لا spam check-ins (max 1 في الأسبوع).
+- لا تخطي الـ at-risk alert إذا تجاوز high.
+- لا تعديل cadence بدون موافقة العميل.
diff --git a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
index d9a05c0f..cbfc7741 100644
--- a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
+++ b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
@@ -342,6 +342,40 @@ OAuth Gmail/Calendar، حصص، سياسات.
**الفرق الشاسع:** Dealix لا يبيع features ولا AI ولا منصة. يبيع **شركة نمو رقمية ذاتية التشغيل** — نتائج منظمة + تشغيل يومي + Proof Pack شهري.
+## 45. Positioning Lock + Customer Ops + Companies/Marketers
+
+**8 modules + 20 endpoints + 44 tests + 2 modes + 7 docs**. التفاصيل:
+
+### Positioning Lock (3 docs)
+- [`POSITIONING_LOCK.md`](POSITIONING_LOCK.md) — category, one-liner, primary buyers (شركات + وكالات), wedge, 5 approved claims, 5 modes, 5 bundles.
+- [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md) — 8 categories of forbidden marketing language + how they're enforced technically.
+- [`APPROVED_MARKET_MESSAGING.md`](APPROVED_MARKET_MESSAGING.md) — tagline, elevator pitch, headlines, 4 outreach segments, social posts, slogan bank.
+
+### Customer Ops (6 modules)
+- `onboarding_checklist`: 8-step Pilot onboarding with progress tracking.
+- `connector_setup_status`: 11 connectors (Gmail/Calendar/Sheets/Moyasar/WhatsApp/Forms/LinkedIn/GBP/CRM/Meet/Instagram) with state machine + ready_for_first_service gate.
+- `support_ticket_router`: 4-tier P0/P1/P2/P3 classification + Arabic first-response templates.
+- `sla_tracker`: per-priority SLA targets + breach detection + weekly health (healthy/watch/critical).
+- `customer_success_cadence`: 6 cadence types + at-risk alerts (risk_score 0..100) + per-bundle 30-day plans + QBR.
+- `incident_router`: SEV1/SEV2/SEV3 triage + auto-SEV1 on data leak / unauthorized send + canonical response plans (PDPL-aware).
+
+### Operator Modes (2 new)
+- `self_growth_mode` — Dealix uses its own OS to find pilots (re-exports targeting_os.self_growth_mode + operator-tier reminders).
+- `service_delivery_mode` — runs client services + tracks SLA + post-delivery handoff to Customer Success.
+
+### Customer-facing Pages (1 new + 1 updated)
+- `landing/companies.html` — Saudi B2B companies. Approval-first, no scraping, 4 bundles.
+- `landing/marketers.html` (updated) — agencies/marketers Agency Growth OS path.
+
+### Customer Ops Docs (5 new)
+- [`ONBOARDING_RUNBOOK.md`](ONBOARDING_RUNBOOK.md) — day-by-day kick-off → first Proof Pack.
+- [`SUPPORT_SLA.md`](SUPPORT_SLA.md) — P0..P3 + classifier keywords.
+- [`INCIDENT_RESPONSE.md`](INCIDENT_RESPONSE.md) — SEV1..SEV3 + post-mortem templates + Arabic comms.
+- [`CUSTOMER_SUCCESS_PLAYBOOK.md`](CUSTOMER_SUCCESS_PLAYBOOK.md) — weekly check-ins, at-risk detection, QBR, renewal.
+- [`CONNECTOR_SETUP_GUIDES.md`](CONNECTOR_SETUP_GUIDES.md) — 11 connectors with scopes + steps + troubleshooting.
+
+**Endpoints:** `/api/v1/customer-ops/*` (20).
+
---
-**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch.
+**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch. اليوم Dealix هو **Saudi Revenue Execution OS** بـ45 طبقة وثائقية، 949 اختبار ناجح، CI أخضر.
diff --git a/dealix/docs/INCIDENT_RESPONSE.md b/dealix/docs/INCIDENT_RESPONSE.md
new file mode 100644
index 00000000..2bcf4147
--- /dev/null
+++ b/dealix/docs/INCIDENT_RESPONSE.md
@@ -0,0 +1,111 @@
+# Dealix Incident Response
+
+> **القاعدة:** أي incident يمر بـ triage → severity → response plan → audit. أي تسريب بيانات أو إرسال غير معتمد = SEV1 تلقائي.
+
+---
+
+## Severities
+
+| Severity | الوصف | First Action | Comm Cadence |
+|----------|------|-------------:|-------------:|
+| **SEV1** | تسريب أمان / إرسال غير معتمد / تعطل كامل | 15 دقيقة | كل 30 دقيقة |
+| **SEV2** | خدمة معطلة لـ ≥5 عملاء | 30 دقيقة | كل ساعة |
+| **SEV3** | تأثير محدود (عميل واحد / degraded) | 2 ساعة | كل 4 ساعات |
+
+---
+
+## Triage Logic
+
+```python
+if has_data_leak or has_unauthorized_send:
+ severity = "SEV1"
+elif affected_customers >= 5:
+ severity = "SEV2"
+else:
+ severity = "SEV3"
+```
+
+**Endpoints:**
+- `POST /api/v1/customer-ops/incidents/triage`
+- `GET /api/v1/customer-ops/incidents/response-plan/{severity}`
+
+---
+
+## Canonical Response Plan (مشترك)
+
+1. **تجميد** الـ live actions على القناة المعنية فوراً.
+2. **إخطار** المؤسس + on-call operator.
+3. **إنشاء** incident channel مع timeline.
+4. **مراجعة** Action Ledger للأفعال المرتبطة.
+5. **إذا تسريب**: إخطار العملاء المتأثرين خلال 72 ساعة (PDPL).
+
+---
+
+## SEV1 Additional Steps
+
+6. تواصل مباشر مع المؤسس + خلية أزمة.
+7. كتابة post-mortem خلال 24 ساعة.
+8. مراجعة قانونية إن لزم (DPA + PDPL implications).
+
+---
+
+## SEV2 Additional Steps
+
+6. تحديث العملاء المتأثرين كل 60 دقيقة.
+7. post-mortem خلال 48 ساعة.
+
+---
+
+## SEV3 Additional Steps
+
+6. تحديث العميل المتأثر مع كل خطوة.
+7. post-mortem اختياري (موصى به للأنماط المتكررة).
+
+---
+
+## Post-Mortem Template
+
+```
+1. ملخص الحادث
+2. timeline (timestamps)
+3. السبب الجذري
+4. ما اشتغل صح
+5. ما اشتغل غلط
+6. الـ action items للوقاية
+7. الـ owner لكل action item
+8. الـ deadline
+```
+
+---
+
+## Communication Templates (Arabic)
+
+### SEV1 — أول ساعة
+> اكتشفنا حدث أمني/تشغيلي يتعلق بـ [نوع الحادث]. أوقفنا الـ live actions على القناة المتأثرة. نتواصل معك خلال 30 دقيقة بتحديث.
+
+### SEV1 — تسريب بيانات
+> نأسف. اكتشفنا تسريب بيانات يتعلق بـ [نوع البيانات]. نراجع الأثر الآن وسنتواصل معك خلال 24 ساعة بتفاصيل + خطوات الحماية. PDPL يلزم بالإبلاغ خلال 72 ساعة لذا سنحرص على إعلامك بكل ما نعرفه.
+
+### SEV2
+> خدمة [اسم الخدمة] متعطلة جزئياً. الفريق يعمل على الإصلاح ونتوقع الاستعادة خلال [وقت]. سنحدثك كل ساعة.
+
+---
+
+## Auto-actions
+
+- **Dealix يجمد القناة تلقائياً** عند detection على:
+ - bounce_rate > 5%
+ - complaint_rate > 0.3%
+ - block_rate WhatsApp > 3%
+- **Dealix يخطر المؤسس** على أي SEV1 detected.
+- **Dealix يضيف entry لـ Action Ledger** لكل incident.
+
+---
+
+## Permission to publish
+
+- Post-mortems خاصة لـ SEV1 لا تُنشر علناً إلا بعد:
+ - مراجعة قانونية.
+ - موافقة العملاء المتأثرين.
+ - إزالة كل PII.
+- Post-mortems لـ SEV2/SEV3 يمكن نشرها كـ engineering blog لو مفيدة.
diff --git a/dealix/docs/ONBOARDING_RUNBOOK.md b/dealix/docs/ONBOARDING_RUNBOOK.md
new file mode 100644
index 00000000..5fc52732
--- /dev/null
+++ b/dealix/docs/ONBOARDING_RUNBOOK.md
@@ -0,0 +1,120 @@
+# Dealix Onboarding Runbook
+
+> **الهدف:** نقل عميل جديد من "وافق على الـ Pilot" إلى "أول Proof Pack" خلال 5 أيام عمل، بدون خطأ تشغيلي.
+
+---
+
+## 8 خطوات الـ onboarding (محسوبة)
+
+| # | الخطوة | المدة | الاعتماد |
+|---|--------|------|---------|
+| 1 | اختيار الهدف | 2د | لا |
+| 2 | اختيار الباقة | 3د | نعم |
+| 3 | بيانات الشركة | 5د | لا |
+| 4 | ربط القنوات (drafts فقط) | 8د | نعم |
+| 5 | رفع قائمة أو ربط مصدر leads | 5د | نعم |
+| 6 | مراجعة المخاطر (PDPL + سمعة) | 4د | نعم |
+| 7 | تشغيل أول خدمة | async | نعم |
+| 8 | استلام أول Proof Pack | async | لا |
+
+**Endpoints:**
+- `POST /api/v1/customer-ops/onboarding/checklist`
+- `POST /api/v1/customer-ops/onboarding/update-step`
+- `GET /api/v1/customer-ops/onboarding/checklist/demo`
+
+---
+
+## Day-by-day
+
+### Day 1 — Kick-off (60 دقيقة)
+- مكالمة 30 دقيقة مع المؤسس / Growth Manager.
+- ملء الـ intake (الخطوات 1-3).
+- توقيع Pilot Agreement draft + DPA draft (يحتاج محامي للحالة الإنتاجية).
+- إنشاء session في `operator_memory` + customer_id.
+
+### Day 2 — Connectors
+- ربط Gmail (drafts فقط) — `connector_setup_status` يتعقّب التقدم.
+- ربط Google Calendar (drafts فقط).
+- ربط Google Sheets للـ exports.
+- WhatsApp Cloud (إذا لازم) — opt-in audit أولاً.
+
+### Day 3 — List + Risk Review
+- رفع CSV / ربط CRM.
+- تشغيل `targeting_os.analyze_uploaded_list_preview`.
+- مراجعة الـ contactability (safe / needs_review / blocked).
+- اعتماد القنوات الآمنة فقط.
+
+### Day 4 — أول خدمة
+- تشغيل First 10 Opportunities Sprint أو List Intelligence.
+- توليد 10 opportunity cards + رسائل عربية.
+- إرسال Approval Pack للعميل (≤3 أزرار لكل بطاقة).
+
+### Day 5 — Proof Pack v1
+- استلام Proof Pack مختصر (PDF + JSON + WhatsApp summary).
+- جلسة مراجعة 30 دقيقة.
+- تفعيل Customer Success Cadence (weekly check-ins).
+
+---
+
+## Connector Setup Status
+
+11 connectors معرّفة في `customer_ops.connector_setup_status`:
+
+| Connector | Default Mode | Phase | Blocking |
+|-----------|--------------|------:|----------|
+| gmail | draft_only | 1 | لا |
+| google_calendar | draft_only | 1 | لا |
+| google_sheets | approved_execute | 1 | لا |
+| moyasar | manual | 1 | لا |
+| whatsapp_cloud | draft_only | 1 | **نعم** |
+| website_forms | approved_execute | 1 | لا |
+| linkedin_lead_forms | ingest_only | 2 | لا |
+| google_business_profile | draft_only | 2 | لا |
+| crm_generic | draft_only | 2 | لا |
+| google_meet | ingest_only | 2 | لا |
+| instagram_graph | ingest_only | 3 | لا |
+
+`ready_for_first_service` = `True` فقط عندما لا يوجد blocking connector مفقود + ≥1 connector connected.
+
+---
+
+## Connector States
+
+```
+not_started → configuring → connected_draft_only
+ → connected_approved_execute
+configuring → failed (يحتاج إعادة محاولة)
+configuring → skipped (إذا قرر العميل عدم الربط)
+```
+
+---
+
+## ما لا يحدث بدون اعتماد
+
+- ربط Gmail لا يفعّل send.
+- ربط Calendar لا يفعّل insert.
+- ربط Moyasar لا يفعّل charge.
+- ربط WhatsApp لا يفعّل cold send.
+
+كل live action يحتاج env flag صريح + اعتماد بشري.
+
+---
+
+## Onboarding Failure Recovery
+
+| فشل | الإجراء |
+|-----|--------|
+| OAuth Gmail فشل | recheck scopes, retry, fallback to draft-only |
+| Moyasar invoice غير موصول | استخدم dashboard manual |
+| العميل لم يرفع قائمة | اعرض Free Diagnostic + recommend_accounts |
+| Risk review كشف مشكلة | توقّف، أرسل تقرير للمؤسس |
+
+---
+
+## Acceptance Criteria
+
+العميل onboarded إذا:
+1. كل الـ 8 خطوات `completed=True` (إلا الـ async منها).
+2. `ready_for_first_service=True`.
+3. Proof Pack v1 تم تسليمه + اعتماده.
+4. Customer Success cadence مفعّل.
diff --git a/dealix/docs/POSITIONING_LOCK.md b/dealix/docs/POSITIONING_LOCK.md
new file mode 100644
index 00000000..912af177
--- /dev/null
+++ b/dealix/docs/POSITIONING_LOCK.md
@@ -0,0 +1,123 @@
+# Dealix Positioning Lock
+
+> **هذا الملف ثابت.** أي تغيير في الـ positioning يحتاج اعتماد المؤسس فقط.
+> لا تكتب landing page ولا رسالة بيع ولا ديمو يخالف هذا الملف.
+
+---
+
+## Category
+
+**Saudi Revenue Execution OS**
+
+ليس CRM. ليس بوت واتساب. ليس lead scraper. ليس AI chatbot عام. ليس وكالة تقليدية.
+
+---
+
+## One-liner (الجملة الرسمية الوحيدة)
+
+> Dealix يشغّل النمو للشركات السعودية: يكتشف الفرص، يكتب الرسائل، يطلب الموافقات، ينسق الاجتماعات، ويثبت العائد.
+
+---
+
+## Primary buyers (مساران فقط)
+
+### 1. الشركات (B2B services / SaaS / training / clinics / real estate)
+- مؤسس / CEO / Growth Manager.
+- يحتاج فرص + رسائل + اجتماعات + Proof Pack.
+- ميزانية 499–10,000 ريال شهرياً.
+
+### 2. الوكالات والمسوقين
+- صاحب وكالة / Head of Sales في وكالة.
+- يبحث عن Growth OS لعملائها.
+- يبحث عن revenue share + co-branded Proof Pack.
+
+---
+
+## Wedge (نقطة الدخول للسوق)
+
+**First 10 Opportunities + Proof Pack** — خدمة Sprint 7 أيام بـ 499 ريال.
+
+كل شيء آخر ينمو من هذه النقطة.
+
+---
+
+## Approved Claims (5 — ما يُسمح بقوله)
+
+1. "نطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack خلال 7 أيام."
+2. "Approval-first — لا نرسل أي شيء بدون موافقتك."
+3. "PDPL-aware — لا cold WhatsApp بدون opt-in."
+4. "Multi-channel orchestration: Email + WhatsApp + Calendar + Sheets + LinkedIn Lead Forms."
+5. "Saudi Tone — رسائل عربية طبيعية، ليست ترجمة."
+
+---
+
+## Prohibited Claims (ما لا يُقال أبداً)
+
+راجع [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md).
+
+---
+
+## Headlines
+
+### Homepage
+> Dealix — Saudi Revenue Execution OS
+
+### Companies page
+> حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack.
+
+### Agencies / marketers page
+> شغّل Dealix لعملائك كـ Agency Growth OS.
+
+### Private Beta page
+> 10 فرص في 10 دقائق + متابعة + Proof Pack — وأنت توافق قبل أي تواصل.
+
+---
+
+## Modes (الـ 5 أوضاع التشغيلية)
+
+1. **CEO Mode** — قرارات يومية، اعتمادات، Proof، مخاطر، 3 خطوات تالية.
+2. **Growth Manager Mode** — حملات، أهداف، متابعات، اجتماعات، صحة القنوات.
+3. **Agency Partner Mode** — إضافة عميل، diagnostic، Proof Pack مشترك العلامة، revenue share.
+4. **Self-Growth Mode** — Dealix يستهدف عملاءه بنفس النظام.
+5. **Service Delivery Mode** — تشغيل خدمات العميل، SLA، deliverables، proof.
+
+---
+
+## Bundles (5 — ما يُعرض للعميل)
+
+1. Growth Starter — 499 ريال.
+2. Data to Revenue — 1,500 ريال.
+3. Executive Growth OS — 2,999 ريال شهرياً.
+4. Partnership Growth — 3,000–7,500 ريال.
+5. Full Growth Control Tower — Custom.
+
+---
+
+## What Dealix is NOT (إنفاذ صارم)
+
+- **ليس CRM** — لا نخزّن جميع بيانات العملاء، لا نحاول استبدال HubSpot.
+- **ليس bot** — لا نتظاهر بأن AI يفعل كل شيء؛ هناك human approval في كل خطوة.
+- **ليس lead scraper** — مصادر مصرّح بها فقط.
+- **ليس وكالة بشرية** — نظام قابل للتكرار بـ Proof Pack محسوب.
+- **ليس AI agent عام** — لكل خدمة sandbox + policy + budget.
+- **ليس Tier-1 enterprise platform** — SMB-first، Saudi-first.
+
+---
+
+## Core Workflow (لا يتغير)
+
+```
+Signal → Context → Service Recommendation → Workflow →
+Risk Check → Draft → Approval → Execution/Export →
+Outcome → Proof → Learning → Upgrade
+```
+
+كل event داخل Dealix يمر بهذه السلسلة.
+
+---
+
+## Why this lock matters
+
+السوق يدعم هذا الاتجاه (HubSpot Growth Context, Gong Revenue AI OS, Salesforce Agentic Work Units), لكن **Dealix لا يحاول أن يكون أكبر منهم**. يحاول أن يكون **أوضح، أسرع، أعمق محلياً، وأقرب للإيراد** للشركات السعودية والوكالات.
+
+أي تشتت في الـ positioning يكلّفنا 3 أشهر من الإنتاجية. لذلك هذا الملف ثابت.
diff --git a/dealix/docs/PROHIBITED_CLAIMS.md b/dealix/docs/PROHIBITED_CLAIMS.md
new file mode 100644
index 00000000..705a234b
--- /dev/null
+++ b/dealix/docs/PROHIBITED_CLAIMS.md
@@ -0,0 +1,107 @@
+# Prohibited Claims — ممنوع تماماً
+
+> أي landing page / رسالة بيع / ديمو / قائمة مزايا تحتوي إحدى هذه العبارات يجب رفضها فوراً.
+
+---
+
+## 1. ادعاءات نتائج مضمونة
+
+- ❌ "نضمن لك عملاء"
+- ❌ "نضمن مبيعات"
+- ❌ "نتائج مضمونة 100%"
+- ❌ "ROI مضمون 10x"
+- ❌ "Money-back guarantee" (إلا في حالة Pilot واضح بشروط محدودة)
+
+**القاعدة:** نقول "Proof Pack بالأرقام" بدلاً من "نتيجة مضمونة".
+
+---
+
+## 2. ادعاءات scraping أو بيانات غير مصرّح بها
+
+- ❌ "نسحب كل بيانات LinkedIn"
+- ❌ "نستخرج جميع الأرقام من Google"
+- ❌ "نجمع leads من أي مكان"
+- ❌ "نحصل على إيميلات decision makers من Apollo"
+
+**القاعدة:** نقول "مصادر مصرّح بها: CRM، LinkedIn Lead Forms، website forms، manual research معتمد".
+
+---
+
+## 3. ادعاءات automation كاملة
+
+- ❌ "نرسل تلقائياً للجميع"
+- ❌ "Dealix يدير كل شيء بدونك"
+- ❌ "Auto-DM على LinkedIn"
+- ❌ "Cold WhatsApp campaigns جاهزة"
+
+**القاعدة:** نقول "Approval-first — لا إرسال بدون موافقتك. Drafts فقط افتراضياً".
+
+---
+
+## 4. ادعاءات تجاوز الموافقات
+
+- ❌ "بدون مكالمة"
+- ❌ "بدون فريق"
+- ❌ "بدون مراجعة"
+- ❌ "Ai-only — لا تدخل بشري"
+
+**القاعدة:** نقول "بشرية القرار، آلية التنفيذ — Approval Center في كل خطوة".
+
+---
+
+## 5. ادعاءات منصات منافسة
+
+- ❌ "بديل HubSpot"
+- ❌ "أرخص من Salesforce"
+- ❌ "نقتل CRM التقليدي"
+- ❌ "أقوى من Gong"
+
+**القاعدة:** نقول "Saudi Revenue Execution OS — يكمّل CRMs، لا يستبدلها".
+
+---
+
+## 6. ادعاءات قانونية/مالية
+
+- ❌ "نتجاوز PDPL"
+- ❌ "نخفي بياناتك من الجهات الرسمية"
+- ❌ "نضمن عودة استثمارك"
+- ❌ "Tax-deductible automatically"
+
+**القاعدة:** نقول "PDPL-aware. DPA draft جاهز. أي عقد يحتاج مراجعة قانونية".
+
+---
+
+## 7. ادعاءات طبية أو جدية
+
+- ❌ "يعالج مشاكل العمل"
+- ❌ "يشفي شركتك من الركود"
+- ❌ "علاج مضمون لقلة العملاء"
+
+**القاعدة:** لا تستخدم لغة طبية أو علاجية. نقول "نحسّن الـ pipeline".
+
+---
+
+## 8. ادعاءات سرعة مبالغ فيها
+
+- ❌ "10 عملاء خلال 24 ساعة"
+- ❌ "مليون ريال خلال شهر"
+- ❌ "نمو 1000% أسبوعياً"
+
+**القاعدة:** نقول "10 فرص خلال 7 أيام، ضمن workflow approval-first".
+
+---
+
+## كيف نفرضها تقنياً
+
+1. **Safety Eval** — `agent_observability.safety_eval()` يكتشف "ضمان 100%" و"آخر فرصة" تلقائياً.
+2. **Saudi Tone Eval** — يرفض "best-in-class" و"synergy".
+3. **Quality Review Gate** — في `service_excellence.quality_review` أي خدمة بدون proof_metrics blocked.
+4. **Tool Action Planner** — يحظر LinkedIn scraping و auto-DM في الكود مباشرة.
+5. **Test `test_positioning_lock.py`** — يفحص landing pages وlanding/services.html والـ docs ضد هذه القائمة.
+
+---
+
+## القاعدة الذهبية
+
+> **لو تحتاج إثبات قبل القول، لا تقله.**
+> Dealix يبيع نتائج محسوبة بـ Proof Pack، لا وعود تسويقية.
diff --git a/dealix/docs/SUPPORT_SLA.md b/dealix/docs/SUPPORT_SLA.md
new file mode 100644
index 00000000..3c9db508
--- /dev/null
+++ b/dealix/docs/SUPPORT_SLA.md
@@ -0,0 +1,95 @@
+# Dealix Support SLA
+
+> **القاعدة:** كل tickets تُصنّف P0/P1/P2/P3 آلياً، لها أهداف first-response و resolution محددة، ويتم تتبع كل تجاوز.
+
+---
+
+## Priority Tiers
+
+| Priority | الوصف | First Response | Resolution Target | Owner |
+|----------|------|---------------:|------------------:|-------|
+| **P0** | حرج جداً — أمان / إرسال خاطئ / تعطل كامل | 30 دقيقة | 4 ساعات | Founder |
+| **P1** | خدمة مهمة معطلة | 2 ساعة | 24 ساعة | Operator on-call |
+| **P2** | Connector أو Proof Pack متأخر | 8 ساعات | 72 ساعة | Operator on-call |
+| **P3** | سؤال عام / تحسين | 24 ساعة | 7 أيام | Operator team |
+
+---
+
+## Endpoints
+
+```
+POST /api/v1/customer-ops/support/classify # تصنيف ticket → priority
+POST /api/v1/customer-ops/support/route # routing مع SLA + first response template
+POST /api/v1/customer-ops/sla/event # تسجيل opened/first_response/resolved/escalated
+POST /api/v1/customer-ops/sla/classify-breach # تحديد إن كان في breach
+POST /api/v1/customer-ops/sla/health-report # تقرير صحة SLA من tickets list
+GET /api/v1/customer-ops/sla/health-report/demo # demo
+```
+
+---
+
+## Auto-classification Keywords
+
+### P0 (حرج جداً)
+- أمان
+- تسريب
+- إرسال خاطئ
+- إرسال بدون موافقة / بدون موافقتي
+- secret / leak / data breach
+- outage / completely down
+- live charge / charge بدون موافقة
+- unauthorized
+
+### P1 (خدمة معطلة)
+- service down / خدمة معطلة
+- service failed
+- Pilot stopped
+- Proof Pack مفقود
+
+### P2 (connector أو proof)
+- connector / Gmail / Calendar / Sheets
+- WhatsApp setup
+- Moyasar invoice
+
+### P3 (افتراضي)
+أي ticket لم يتطابق مع P0/P1/P2.
+
+---
+
+## First-Response Templates
+
+كل priority لها قالب رد أولي عربي معد مسبقاً عبر `build_first_response_template(priority)`.
+
+### مثال P0
+> وصلني بلاغك الآن. نتعامل معه كأولوية حرجة. سأرد عليك خلال 30 دقيقة بتفاصيل ما حدث + الإجراءات المتخذة. إذا اكتشفت أي إرسال غير معتمد أو تسريب بيانات، سأتواصل معك مباشرة.
+
+---
+
+## Health Report Verdict
+
+عبر `build_sla_health_report`:
+- **healthy**: breach_rate < 10%
+- **watch**: 10% ≤ breach_rate < 25%
+- **critical**: breach_rate ≥ 25%
+
+عند `critical` → escalate تلقائي للمؤسس + إيقاف الـ live actions حتى المراجعة.
+
+---
+
+## Weekly SLA Review
+
+كل اثنين:
+1. تجميع كل tickets الأسبوع المنقضي.
+2. تشغيل `build_sla_health_report`.
+3. مراجعة الـ breaches.
+4. تحديث `customer_success_cadence` للعملاء المتأثرين.
+5. إذا critical → post-mortem + `incident_router`.
+
+---
+
+## ما لا يحدث في الـ support
+
+- لا response تلقائي للعميل بدون مراجعة بشرية.
+- لا تسريب لـ ticket id في القنوات العامة.
+- لا فتح ticket بـ priority < classified-priority (الـ system يحدد، البشر يرفع فقط).
+- لا إغلاق ticket بدون تأكيد من العميل.
diff --git a/dealix/landing/companies.html b/dealix/landing/companies.html
new file mode 100644
index 00000000..174df91b
--- /dev/null
+++ b/dealix/landing/companies.html
@@ -0,0 +1,125 @@
+
+
+
+
+
+Dealix للشركات — Saudi Revenue Execution OS
+
+
+
+
+
+
+
+ المشكلة
+
+ - عندك إيميل وقائمة عملاء قدامى وقنوات نشطة، لكن لا تعرف وش أهم شيء اليوم.
+ - الفريق يقضي وقت كبير على الـ outreach بدون نتائج محسوبة.
+ - تخاف من حظر القناة لو أرسلت بدون عناية.
+ - لا يوجد Proof واضح للإدارة عن العائد.
+
+
+
+
+ كيف يعمل Dealix
+
+ - اختر هدفك: عملاء جدد / استخدام قائمة / شراكات / تشغيل يومي.
+ - Dealix يوصي بالخدمة + يجمع intake.
+ - يطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة.
+ - كل رسالة تنتظر اعتمادك قبل الإرسال.
+ - Proof Pack أسبوعي + Founder Shadow Board شهري.
+
+
+
+
+ الباقات
+
+
+
Growth Starter
+
499 ريال
+
Free Diagnostic + First 10 Opportunities + Proof Pack مختصر
+
+
+
Data to Revenue
+
1,500 ريال
+
List Intelligence + Top 50 Targets + رسائل + Risk Report
+
+
+
Executive Growth OS
+
2,999 ريال شهرياً
+
Daily Brief + Approvals + Proof Pack أسبوعي
+
+
+
Full Growth Control Tower
+
Custom
+
30 يوم — كل الخدمات على مراحل
+
+
+
+
+
+ ضمانات Dealix:
+ Approval-first في كل قناة. لا scraping. لا cold WhatsApp.
+ لا charge بدون موافقة. لا وعود مضمونة. Proof Pack بالأرقام.
+
+
+
+ Proof Pack
+
+ - Opportunities created.
+ - Drafts created + approved.
+ - Replies received + meetings drafted.
+ - Pipeline influenced (SAR).
+ - Risks blocked (مخاطر منعت تلقائياً).
+ - Time saved (ساعات).
+
+
+
+
+ الأمان والامتثال
+
+ - Approval-first في كل قناة — لا live send بدون اعتمادك.
+ - PDPL-aware: لا cold WhatsApp، DPA draft جاهز.
+ - Secret redactor + Patch firewall + Trace redactor.
+ - Saudi Tone + Safety eval قبل كل رسالة.
+ - Action Ledger يسجّل كل فعل + من اعتمده.
+
+
+
+
+
+
+
diff --git a/dealix/tests/unit/test_customer_ops.py b/dealix/tests/unit/test_customer_ops.py
new file mode 100644
index 00000000..1c304764
--- /dev/null
+++ b/dealix/tests/unit/test_customer_ops.py
@@ -0,0 +1,268 @@
+"""Unit tests for Customer Ops."""
+
+from __future__ import annotations
+
+import pytest
+
+from auto_client_acquisition.customer_ops import (
+ SUPPORT_PRIORITIES,
+ SUPPORTED_CONNECTORS,
+ build_at_risk_alert,
+ build_connector_setup_summary,
+ build_customer_success_plan,
+ build_first_response_template,
+ build_incident_response_plan,
+ build_onboarding_checklist,
+ build_sla_health_report,
+ build_weekly_check_in,
+ classify_sla_breach,
+ classify_ticket_priority,
+ record_sla_event,
+ route_ticket,
+ triage_incident,
+ update_connector_status,
+ update_onboarding_step,
+)
+
+
+# ── Onboarding ───────────────────────────────────────────────
+def test_onboarding_checklist_has_8_steps():
+ out = build_onboarding_checklist(customer_id="c1")
+ assert out["total_steps"] == 8
+ assert out["current_step_id"] == "select_goal"
+
+
+def test_update_onboarding_step_completes():
+ cl = build_onboarding_checklist(customer_id="c1")
+ cl = update_onboarding_step(cl, step_id="select_goal", completed=True)
+ assert cl["progress_pct"] == 12.5
+ assert cl["current_step_id"] == "select_bundle"
+
+
+def test_update_onboarding_step_unknown():
+ cl = build_onboarding_checklist(customer_id="c1")
+ cl = update_onboarding_step(cl, step_id="bogus_step")
+ assert "error" in cl
+
+
+def test_complete_all_onboarding_steps():
+ cl = build_onboarding_checklist(customer_id="c1")
+ for s in list(cl["steps"]):
+ cl = update_onboarding_step(cl, step_id=s["id"], completed=True)
+ assert cl["progress_pct"] == 100.0
+ assert cl["current_step_id"] == "done"
+
+
+# ── Connectors ───────────────────────────────────────────────
+def test_supported_connectors_includes_critical():
+ keys = {c["key"] for c in SUPPORTED_CONNECTORS}
+ for required in ("gmail", "google_calendar", "moyasar", "whatsapp_cloud",
+ "google_sheets", "website_forms", "linkedin_lead_forms"):
+ assert required in keys
+
+
+def test_connector_summary_with_blocking_missing():
+ out = build_connector_setup_summary(
+ customer_id="c1",
+ statuses={"gmail": {"state": "connected_draft_only"}},
+ )
+ assert "whatsapp_cloud" in out["blocking_missing"]
+ assert out["ready_for_first_service"] is False
+
+
+def test_connector_summary_ready():
+ out = build_connector_setup_summary(
+ customer_id="c1",
+ statuses={
+ "gmail": {"state": "connected_draft_only"},
+ "whatsapp_cloud": {"state": "connected_draft_only"},
+ },
+ )
+ assert out["ready_for_first_service"] is True
+
+
+def test_update_connector_status_validates():
+ statuses: dict = {}
+ with pytest.raises(ValueError):
+ update_connector_status(statuses, connector_key="gmail",
+ state="totally_invalid")
+
+
+def test_update_connector_status_writes():
+ statuses: dict = {}
+ update_connector_status(statuses, connector_key="gmail",
+ state="connected_draft_only")
+ assert statuses["gmail"]["state"] == "connected_draft_only"
+
+
+# ── Support routing ──────────────────────────────────────────
+def test_classify_p0_for_security_keywords():
+ out = classify_ticket_priority("اكتشفت تسريب في trace logs")
+ assert out["priority"] == "P0"
+
+
+def test_classify_p0_for_unauthorized_send():
+ out = classify_ticket_priority("Dealix أرسل رسالة بدون موافقتي")
+ assert out["priority"] == "P0"
+
+
+def test_classify_p1_for_service_down():
+ out = classify_ticket_priority("Pilot stopped working today")
+ assert out["priority"] == "P1"
+
+
+def test_classify_p2_for_connector_issue():
+ out = classify_ticket_priority("My Gmail connector won't authenticate")
+ assert out["priority"] == "P2"
+
+
+def test_classify_p3_default():
+ out = classify_ticket_priority("سؤال بسيط عن الأسعار")
+ assert out["priority"] == "P3"
+
+
+def test_classify_empty_returns_p3():
+ out = classify_ticket_priority("")
+ assert out["priority"] == "P3"
+
+
+def test_route_ticket_includes_sla():
+ out = route_ticket(text="تسريب أمان", customer_id="c1")
+ assert out["priority"] == "P0"
+ assert out["sla"]["first_response_minutes"] == 30
+ assert out["live_send_allowed"] is False
+
+
+def test_first_response_p0_arabic():
+ out = build_first_response_template("P0")
+ assert "30 دقيقة" in out["body_ar"]
+ assert out["live_send_allowed"] is False
+
+
+def test_support_priorities_count():
+ assert len(SUPPORT_PRIORITIES) == 4
+
+
+# ── SLA ──────────────────────────────────────────────────────
+def test_sla_event_validates():
+ with pytest.raises(ValueError):
+ record_sla_event(ticket_id="t1", priority="P0", event="bogus")
+
+
+def test_sla_event_appends_to_log():
+ log: list = []
+ record_sla_event(ticket_id="t1", priority="P0", event="opened", log=log)
+ assert len(log) == 1
+
+
+def test_classify_breach_within_target():
+ out = classify_sla_breach(
+ priority="P0", minutes_to_first_response=20, hours_to_resolve=3,
+ )
+ assert out["breached"] is False
+
+
+def test_classify_breach_exceeded():
+ out = classify_sla_breach(
+ priority="P0", minutes_to_first_response=120, hours_to_resolve=10,
+ )
+ assert out["breached"] is True
+ assert len(out["breaches"]) == 2
+
+
+def test_sla_health_report_aggregates():
+ out = build_sla_health_report(tickets=[
+ {"priority": "P0", "first_response_min": 12, "resolution_hours": 2},
+ {"priority": "P1", "first_response_min": 90, "resolution_hours": 18},
+ {"priority": "P3", "first_response_min": 1500, "resolution_hours": 200},
+ ])
+ assert out["total_tickets"] == 3
+ assert out["total_breached"] == 1 # only P3 breached
+
+
+def test_sla_health_verdict_critical():
+ out = build_sla_health_report(tickets=[
+ {"priority": "P0", "first_response_min": 60, "resolution_hours": 10},
+ {"priority": "P0", "first_response_min": 120, "resolution_hours": 20},
+ {"priority": "P0", "first_response_min": 180, "resolution_hours": 30},
+ {"priority": "P0", "first_response_min": 240, "resolution_hours": 40},
+ ])
+ assert out["verdict"] == "critical"
+
+
+# ── Incidents ───────────────────────────────────────────────
+def test_triage_data_leak_is_sev1():
+ out = triage_incident(
+ title="Possible data exposure",
+ has_data_leak=True,
+ )
+ assert out["severity"] == "SEV1"
+
+
+def test_triage_unauthorized_send_is_sev1():
+ out = triage_incident(
+ title="Unauthorized message",
+ has_unauthorized_send=True,
+ )
+ assert out["severity"] == "SEV1"
+
+
+def test_triage_many_customers_is_sev2():
+ out = triage_incident(
+ title="Service outage",
+ affected_customers=10,
+ )
+ assert out["severity"] == "SEV2"
+
+
+def test_triage_single_customer_is_sev3():
+ out = triage_incident(title="Customer X has issue", affected_customers=1)
+ assert out["severity"] == "SEV3"
+
+
+def test_incident_response_plan_sev1_includes_pdpl():
+ out = build_incident_response_plan(severity="SEV1")
+ text = " ".join(out["plan_ar"])
+ assert "PDPL" in text
+
+
+# ── Customer Success ────────────────────────────────────────
+def test_weekly_check_in_arabic():
+ out = build_weekly_check_in(
+ customer_id="c1", company_name="Acme",
+ metrics={"drafts_approved": 5, "replies": 2,
+ "meetings": 1, "risks_blocked": 3, "pipeline_sar": 18000},
+ )
+ assert out["type"] == "weekly_check_in"
+ assert any("Pipeline" in tp for tp in out["talking_points_ar"])
+
+
+def test_at_risk_alert_high_severity():
+ out = build_at_risk_alert(
+ customer_id="c1", days_inactive=20,
+ drafts_pending=15, last_proof_pack_days_ago=21,
+ )
+ assert out["severity"] == "high"
+ assert out["risk_score"] >= 60
+
+
+def test_at_risk_alert_low_severity():
+ out = build_at_risk_alert(
+ customer_id="c1", days_inactive=2,
+ drafts_pending=1, last_proof_pack_days_ago=3,
+ )
+ assert out["severity"] == "low"
+
+
+def test_customer_success_plan_for_growth_starter():
+ out = build_customer_success_plan(
+ customer_id="c1", bundle_id="growth_starter",
+ )
+ assert any("Day 1" in line for line in out["cadence_ar"])
+
+
+def test_customer_success_plan_for_executive():
+ out = build_customer_success_plan(
+ customer_id="c1", bundle_id="executive_growth_os",
+ )
+ assert any("Founder Shadow Board" in line for line in out["cadence_ar"])
diff --git a/dealix/tests/unit/test_positioning_lock.py b/dealix/tests/unit/test_positioning_lock.py
new file mode 100644
index 00000000..498f47c7
--- /dev/null
+++ b/dealix/tests/unit/test_positioning_lock.py
@@ -0,0 +1,141 @@
+"""Positioning Lock tests — enforce category rules + prohibited claims."""
+
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+
+# Positive claims that must NEVER appear in customer-facing pages.
+# (Negative restatements like "no auto-DM" in safety sections are fine —
+# we only block positive claims that promise forbidden behavior.)
+PROHIBITED_PHRASES = (
+ "نضمن لك عملاء",
+ "نضمن مبيعات",
+ "نتائج مضمونة 100%",
+ "ضمان مضمون",
+ "مليون ريال خلال شهر",
+ "نسحب كل بيانات LinkedIn",
+ "نقوم بـ auto-DM",
+ "نتجاوز PDPL",
+ "بدون مراجعة بشرية",
+ "AI-only — لا تدخل بشري",
+ "بديل HubSpot",
+ "أرخص من Salesforce",
+ "نقتل CRM",
+)
+
+# Required claims that should appear in the positioning + market messaging docs.
+REQUIRED_CLAIMS_FRAGMENTS_AR = (
+ "Approval-first",
+ "PDPL",
+ "Saudi Tone",
+ "Proof Pack",
+)
+
+
+def _read(rel_path: str) -> str:
+ p = REPO_ROOT / rel_path
+ if not p.exists():
+ return ""
+ return p.read_text(encoding="utf-8", errors="ignore")
+
+
+def test_positioning_lock_exists():
+ text = _read("docs/POSITIONING_LOCK.md")
+ assert text, "POSITIONING_LOCK.md missing"
+ assert "Saudi Revenue Execution OS" in text
+ assert "ليس CRM" in text
+ assert "ليس بوت واتساب" in text
+
+
+def test_prohibited_claims_doc_exists():
+ text = _read("docs/PROHIBITED_CLAIMS.md")
+ assert text, "PROHIBITED_CLAIMS.md missing"
+ assert "نضمن" in text
+ assert "scraping" in text.lower()
+
+
+def test_approved_market_messaging_doc_exists():
+ text = _read("docs/APPROVED_MARKET_MESSAGING.md")
+ assert text, "APPROVED_MARKET_MESSAGING.md missing"
+ for fragment in REQUIRED_CLAIMS_FRAGMENTS_AR:
+ assert fragment in text, f"missing required fragment: {fragment}"
+
+
+def test_no_prohibited_phrases_in_landing_pages():
+ """Customer-facing landing pages must NOT contain prohibited claims."""
+ pages = [
+ "landing/private-beta.html",
+ "landing/services.html",
+ "landing/free-diagnostic.html",
+ "landing/first-10-opportunities.html",
+ "landing/agency-partner.html",
+ "landing/list-intelligence.html",
+ "landing/growth-os.html",
+ "landing/companies.html",
+ ]
+ failures: list[str] = []
+ for page in pages:
+ text = _read(page)
+ if not text:
+ continue # page doesn't exist
+ for bad in PROHIBITED_PHRASES:
+ if bad in text:
+ failures.append(f"{page} contains prohibited phrase: {bad}")
+ assert not failures, "Prohibited phrases found:\n" + "\n".join(failures)
+
+
+def test_companies_page_has_approved_messaging():
+ text = _read("landing/companies.html")
+ assert text, "landing/companies.html missing"
+ assert "Approval-first" in text or "approval-first" in text.lower()
+ # Should reference Proof Pack
+ assert "Proof Pack" in text
+
+
+def test_marketers_or_agency_page_exists():
+ """At least one of the agency-facing pages must exist."""
+ a = _read("landing/agency-partner.html")
+ m = _read("landing/marketers.html")
+ assert a or m, "Need at least one of agency-partner.html or marketers.html"
+
+
+def test_private_beta_page_no_guarantees():
+ text = _read("landing/private-beta.html")
+ assert text, "private-beta.html missing"
+ assert "نضمن" not in text or "لا نضمن" in text
+ assert "guarantee" not in text.lower() or "no guarantee" in text.lower()
+
+
+def test_revenue_today_playbook_emphasizes_approval():
+ text = _read("docs/REVENUE_TODAY_PLAYBOOK.md")
+ assert text, "REVENUE_TODAY_PLAYBOOK.md missing"
+ assert "Approval-first" in text or "approval" in text.lower()
+ # Must explicitly state no live charge
+ assert "live charge" in text.lower() or "API charge" in text or "manual" in text.lower()
+
+
+def test_positioning_lock_has_5_bundles():
+ text = _read("docs/POSITIONING_LOCK.md")
+ for bundle in (
+ "Growth Starter",
+ "Data to Revenue",
+ "Executive Growth OS",
+ "Partnership Growth",
+ "Full Growth Control Tower",
+ ):
+ assert bundle in text, f"missing bundle in POSITIONING_LOCK.md: {bundle}"
+
+
+def test_positioning_lock_lists_5_modes():
+ text = _read("docs/POSITIONING_LOCK.md")
+ for mode in (
+ "CEO Mode",
+ "Growth Manager Mode",
+ "Agency Partner Mode",
+ "Self-Growth Mode",
+ "Service Delivery Mode",
+ ):
+ assert mode in text, f"missing mode in POSITIONING_LOCK.md: {mode}"