# Veedon Detection Engine (VDE) — محرك الكشف المعياري على الحافة

> **الهدف:** برنامج على صندوق الحافة يغطّي نقص Frigate — تضيف له **موديولات كشف**
> (سلوكي / أشياء / مخصّص) تُحمَّل **وقت التشغيل** بلا إعادة تشغيل. يُوقظها حدث **MQTT**
> من Frigate (شخص/حركة/منطقة)، فيحلّل الموديول اللقطات/الفيديو عبر **VLM سحابي** أو
> **نموذج ONNX مدرّب**، ثم يرسل التنبيه + صورة الحدث للـdashboard. **كله يُدار عن بُعد.**
>
> شبيه فكرة [Double-Take](https://github.com/jakowenko/double-take) (طبقة فوق Frigate عبر
> MQTT) لكن **معمّم**: بدل الوجوه فقط → أي كاشف قابل للإضافة كموديول.

آخر تحديث: 2026-07-05

---

## 0. لماذا VDE (فجوة Frigate)

Frigate ممتاز في: كشف أجسام COCO + تتبّع + مناطق + تسجيل + MQTT. **ينقصه:**
- الكشف **السلوكي** (عراك/تنمّر/سقوط/سرقة) — يحتاج VLM أو نموذج زمني.
- الكشف **المخصّص** لأصناف خارج COCO (أسماك، دواجن نافقة، عيوب منتج) بلا إعادة بناء المحرك.
- **إضافة كاشف جديد وقت التشغيل** عن بُعد بلا لمس الجهاز.

VDE = طبقة تنسيق فوق Frigate تسدّ هذا الثلاثي عبر **نظام موديولات (plugins) ساخن**.

---

## 1. المعمارية

```
┌──────────────────────── صندوق الحافة ────────────────────────┐
│  كاميرا ──RTSP──► veedon-engine (Frigate + go2rtc)            │
│                     │ كشف أجسام+حركة    │ restream (بثّ موحّد) │
│              MQTT ◄──┘                   └──► (بثّ حي)          │
│                ▼ frigate/events, /motion        │             │
│  ┌──────────── Veedon Detection Engine (VDE) ───┼──────────┐  │
│  │  (أ) MQTT subscriber → موزّع الأحداث  [افتراضي]         │  │
│  │  (ب) Stream workers ◄── يقرأ البث الحي مباشرة ◄─────────┘  │
│  │      كلاهما → موديولات الكشف:                             │  │
│  │  ┌──────────┬───────────┬──────────┬──────────────────┐  │  │
│  │  │ rule     │ edge_model│ vlm      │ custom (fish…)   │  │  │
│  │  │ (مناطق)  │ (ONNX)    │ (سحابي)  │ (كود مرفوع)      │  │  │
│  │  └──────────┴───────────┴──────────┴──────────────────┘  │  │
│  │  loader ساخن (hot-reload) + سجل الموديولات المحلي         │  │
│  └───────┬─────────────────────────────────────────────────┘  │
└──────────│── HTTPS: تنبيه + صورة الحدث + سحب الموديولات OTA ───┘
           ▼
┌──────────────── سحابة Veedon ──────────────────┐
│ بوابة الأجهزة: /events /analyze /modules       │
│ سجل الموديولات (كود موقّع + نموذج + manifest)  │
│ VLM سحابي (نموذج مدرب / LM Studio / Ollama)    │
│ Dashboard: كتالوج موديولات + تفعيل + رفع + OTA │
└─────────────────────────────────────────────────┘
```

**المبدأ:** Frigate يكتشف "شيئاً حدث"؛ VDE يقرّر "ماذا يعني" عبر الموديول المناسب.

---

## 2. واجهة الموديول (Detector API)

كل موديول = ملف بايثون يعرّف صنفاً يرث `Detector`. ثابتة وبسيطة:

```python
# vde/api.py  (تُشحن مع المحرك — الموديولات تستوردها)
from dataclasses import dataclass

@dataclass
class Detection:
    flagged: bool                 # مخالفة تستحق تنبيهاً؟
    score: float = 0.0
    note: str = ""
    label: str = ""
    snapshot: bytes | None = None # صورة الدليل (وإلا يلتقطها المحرك)

class Context:
    """يوفّرها المحرك لكل موديول — لا يصل الموديول للشبكة/الملفات مباشرة."""
    config: dict                  # إعداد الميزة لهذه الكاميرا (من الdashboard)
    model_path: str | None        # مسار ONNX المصاحب (إن وُجد)
    def snapshot(self, camera: str) -> bytes | None: ...          # لقطة حية
    def burst(self, camera: str, n=4, gap=0.4) -> list[bytes]: ... # لقطات متتالية
    def onnx(self, model_path: str): ...      # OnnxDetector مشترك (كسول، مُخزَّن)
    def vlm(self, feature_key: str, frames: list[bytes]) -> dict: ... # VLM سحابي
    def log(self, msg: str) -> None: ...

class Detector:
    key: str                      # "fish_count" — مفتاح الميزة (يطابق الكتالوج)
    kind: str = "custom"          # rule | edge_model | vlm | custom
    triggers: list[str] = ["motion"]
    #   مصدر الإيقاظ (يحدّد أي دالة تُستدعى):
    #     حدثي (MQTT، الافتراضي): "motion" | "object:<label>" | "zone:<name>"
    #                              | "schedule" (دوري كل interval_s)   → on_event
    #     بثّي: "stream" → عامل مخصّص يقرأ البث الحي ويغذّي         → on_frame
    interval_s: int = 0           # لـ schedule
    fps: float = 2                # لـ stream: عدد الإطارات المُحلَّلة/الثانية

    def setup(self, ctx: Context) -> None:      # مرة واحدة عند التحميل (نموذج كسول)
        ...
    def on_event(self, ev: dict, ctx: Context) -> Detection | None:
        ...                                     # الوضع الحدثي (MQTT) — None = لا بلاغ
    def on_frame(self, frame: bytes, ctx: Context) -> Detection | None:
        ...                                     # الوضع البثّي — إطار JPEG من البث الحي
```

المحرك يتكفّل بالباقي حسب المصدر: **حدثي** → يستمع MQTT ويستدعي `on_event`؛ **بثّي** →
يفتح عامل بث لكل (موديول، كاميرا) ويستدعي `on_frame` بمعدّل `fps`. إذا رجع `Detection.flagged`
→ يرسل حدثاً + صورة للسحابة. الموديول لا يفتح شبكة بنفسه — الإطارات تصله عبر المحرك (أمان).

---

## 3. حزمة الموديول (OTA) + التوقيع

كل موديول يُشحن كحزمة موقّعة (لأن **تشغيل كود مرفوع = RCE by design**؛ الأمان = التوقيع + الاعتماد):

```
fish_count-v3.vmod   (zip)
├── manifest.json
├── detector.py       # يعرّف صنف Detector
└── veedon-fish-v1.onnx   (اختياري)
```

```json
// manifest.json
{
  "key": "fish_count",
  "version": 3,
  "kind": "edge_model",
  "entry": "detector.py",
  "model": "veedon-fish-v1.onnx",
  "triggers": ["motion"],
  "sector": "farm",
  "sha256": "<هاش الحزمة بلا حقل التوقيع>",
  "signature": "<Ed25519(sha256) بمفتاح Veedon الخاص>"
}
```

**سلسلة الثقة:**
1. نوقّع الحزمة بمفتاح Veedon **الخاص** (على السحابة).
2. مفتاح Veedon **العام** مضمّن في صورة المحرك على الحافة.
3. قبل أي تحميل: المحرك يتحقق `sha256` ثم يتحقق `signature` بالمفتاح العام → يرفض غير الموقّع/المعدّل.
4. **اعتماد أدمن** لكل موديول جديد من الdashboard (نفس بوابة اعتماد الجهاز) — لا يُحمَّل قبلها.
5. الجهاز أصلاً خلف بوابة الحماية (بصمة + اعتماد) — نسخة غير معتمدة لا تصل للسجل أصلاً.

---

## 4. التحميل الساخن (hot-reload بلا إعادة تشغيل)

- المحرك يستطلع `GET /api/device/modules` (أو يستقبل إشعار `modules_version` مثل `config_version`).
- لكل موديول جديد/محدَّث مفعّل: `GET /api/device/modules/{key}/{version}` → يتحقق التوقيع → يفكّه في `/modules/{key}/`.
- **الاستيراد الساخن** باسم فريد لكل نسخة (يتفادى تخزين import المؤقت):

```python
import importlib.util
def load_module(key, version, path):
    name = f"veedon_mod_{key}_v{version}"
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)                     # يشغّل الكود المرفوع (بعد التحقق)
    det = next(o for o in vars(mod).values()
               if isinstance(o, type) and issubclass(o, Detector) and o is not Detector)()
    det.setup(ctx_for(key))
    REGISTRY[key] = det                              # يستبدل النسخة القديمة ذرّياً
```

- النسخة القديمة تُزال من `REGISTRY` ويجمعها GC. **لا إعادة تشغيل، لا انقطاع** لبقية الكاميرات.
- فشل تحميل/تشغيل موديول لا يُسقط المحرك: `try/except` حول كل استدعاء، وتعطيل تلقائي بعد N أخطاء متتالية + إبلاغ الdashboard.

---

## 5. مصادر الإيقاظ — وضعان لكل موديول

كل موديول (لكل كاميرا) يختار مصدره من الdashboard: **(أ) حدثي MQTT — الافتراضي**، أو
**(ب) بثّي مباشر من الكاميرا**.

### (أ) الوضع الحدثي — MQTT من Frigate  [الافتراضي]

نفعّل MQTT في Frigate (معطّل حالياً) نحو broker محلي (mosquitto في نفس compose):

| Topic | متى | يوقظ trigger |
|---|---|---|
| `frigate/events` | بدء/تحديث/انتهاء تتبّع جسم (JSON: label, camera, zones) | `object:<label>` / `zone:<name>` |
| `frigate/<cam>/motion` | حركة في الكاميرا | `motion` |
| (مؤقّت داخلي) | كل `interval_s` | `schedule` |

الموزّع: لكل رسالة → الموديولات المفعّلة لتلك الكاميرا التي يطابق `triggers` نوعَ الحدث →
`on_event(ev, ctx)`. **الأكفأ:** لا حوسبة إلا حين يرى Frigate شيئاً. مناسب لأغلب الحالات
(PPE عند شخص، منطقة ممنوعة، عدّ عند حركة).

### (ب) الوضع البثّي — تحليل مباشر من بث الكاميرا الحي

للموديول `triggers = ["stream"]` يفتح المحرك **عامل بثّ دائم** لكل (موديول، كاميرا):

- **المصدر:** بثّ **go2rtc/restream** الخاص بـ Frigate (اتصال واحد بالكاميرا، لا مضاعفة)،
  أو RTSP/MJPEG مباشرة للكاميرا كبديل (لكاميرات خارج Frigate).
- يفكّ الترميز (ffmpeg/PyAV) ويعيّن الإطارات بمعدّل `fps` (مثلاً 2/ث) → `on_frame(frame, ctx)`.
- **يعمل باستمرار** بصرف النظر عن Frigate — مناسب لما لا يُصدر Frigate حدثاً له، أو لتحليل
  زمني متّصل (تتابع سقوط/عراك)، أو كاميرات لا يديرها Frigate.
- أثقل (فكّ ترميز مستمر) → يُفعَّل انتقائياً لكاميرات محدّدة؛ يمكن للموديول تجميع إطارات
  متتالية وإرسالها للـVLM السحابي (فيديو/تتابع) عبر `ctx.vlm(...)`.

**متى أيّهما:**

| | حدثي (MQTT) | بثّي (stream) |
|---|---|---|
| الحوسبة | عند الحدث فقط (خفيف) | مستمر (أثقل) |
| زمن الاستجابة | يعتمد على كشف Frigate | فوري/متّصل |
| يحتاج Frigate يرى الجسم | نعم | لا |
| الأنسب لـ | PPE، مناطق، عدّ، أشياء عند حركة | سقوط/عراك زمني، أسماك/سلوك مستمر، كاميرا خارج Frigate |

كلا الوضعين يمرّان بنفس أنواع الموديولات (rule/edge_model/vlm/custom) ونفس مسار الإخراج
(حدث + صورة → السحابة)، مع debounce/cooldown لكل (موديول، كاميرا) لمنع الفيضان.

---

## 6. الإدارة عن بُعد (Dashboard)

- **كتالوج الموديولات** (توسعة لكتالوج المزايا `event_types`): كل موديول صف بـ `key/kind/version/sector`.
- لكل فرع/كاميرا: تفعيل/تعطيل + إعداد JSON (نفس `EdgeDevicePanel` الحالي).
- **رفع موديول جديد** من الdashboard: كود `detector.py` (+نموذج) → السحابة توقّعه وتضيفه للسجل → OTA للأجهزة المعتمدة.
- **إصدارات + تراجع**: رفع v(n+1) يبقي v(n)؛ زر تراجع يعيد النسخة السابقة عن بُعد.
- حالة الموديول لكل جهاز (محمّل/إصدار/آخر خطأ) عبر heartbeat.

---

## 6.5 إدارة الحافة الكاملة عن بُعد (Zero-Touch) — مبدأ حاكم

**العمل اليدوي الوحيد في الموقع = توصيل الكهرباء والشبكة.** كل شيء بعدها يُدار عن بُعد —
Frigate **و** VDE **والصندوق نفسه** — ويجب أن **يصمد أمام الأعطال**: أي إعداد/موديول خاطئ
يُسترجَع عن بُعد بلا زيارة الموقع.

| سطح الإدارة | الطبقة | الآلية | الحالة |
|---|---|---|---|
| الكاميرات/المناطق/المزايا | Frigate | `config_version` → render → restart | ✅ مبني |
| وحدات المحرك (وجوه/لوحات/بحث/تسجيل/صوت/قناع) | Frigate | كتالوج → config | ✅ مبني |
| تحديث نسخة المحرك (صورة Docker) | Frigate | سحب الصورة + إعادة إنشاء الحاوية | ✅ مبني |
| إيقاف/تشغيل (kill switch عدم سداد) | كلاهما | suspend فوري | ✅ مبني |
| اعتماد/بصمة الجهاز + منع VM | الصندوق | بوابة الحماية | ✅ مبني |
| رفع/تفعيل/تعطيل موديولات (كود+نموذج) | VDE | سجل موقّع + `modules_version` | 🔜 مخطط |
| إعداد كل موديول لكل كاميرا + مصدر الإيقاظ (MQTT/بث) | VDE | config JSON | 🔜 مخطط |
| إصدارات الموديولات + **تراجع** | VDE | نسخ متعددة + rollback | 🔜 مخطط |
| **تحديث الوكيل نفسه (VDE agent) OTA** | الصندوق | self-update بنسخة احتياطية آمنة | 🔜 مخطط |
| **سحب السجلات/التشخيص للوحة** | كلاهما | logs عبر heartbeat/طلب | 🔜 مخطط |
| **إعادة تشغيل عن بُعد** (محرك/موديول/وكيل/الصندوق) | كلاهما | أوامر مُبيّتة (لا شل مفتوح) | 🔜 مخطط |
| مراقبة صحة: CPU/RAM/قرص/حرارة/fps/كاميرات up | الصندوق | heartbeat موسّع | جزئي (كاميرات up مبني) |
| نفق للوصول الحي لصفحات Frigate خلف NAT | Frigate | cloudflared/frp | 🔜 (مرحلة Scale) |

**ضمانات "بلا زيارة موقع" (الأهم — بدونها تنهار الفكرة):**
1. **الوكيل = مستوى التحكم الدائم**، يعمل مستقلاً عن المحرك والموديولات؛ محرك/موديول متعطّل **لا يُفقد التحكم عن بُعد**.
2. **تراجع آمن**: الوكيل يحتفظ بآخر إعداد/موديول صالح ويعود إليه تلقائياً عند فشل التطبيق — لا يعلق الفرع على إصدار خاطئ.
3. **Watchdog + auto-recovery**: إعادة تشغيل تلقائية للمحرك/الموديول عند التعطّل، وتعطيل موديول يفشل تكراراً بلا إسقاط الباقي.
4. **self-update بنسخة احتياطية**: تحديث الوكيل يبقي النسخة السابقة ويعود إليها إن فشل الإقلاع (لا bricking).
5. كل ذلك عبر **HTTPS صادر من الحافة** (يعمل خلف أي NAT/جدار ناري) — لا منافذ واردة، لا اعتماد على IP ثابت.

**قاعدة تصميم:** أي ميزة جديدة (Frigate أو VDE) لا تُعتبر مكتملة قبل أن تكون **قابلة للإدارة
والاسترجاع بالكامل عن بُعد**. زيارة الموقع = فشل تصميمي.

---

## 7. مثال كامل: كشف الأسماك (fish_count)

**الهدف:** كاميرا على حوض/مزرعة سمكية — تنبيه عند سلوك غير طبيعي/عدّ.

```python
# detector.py  (يُرفع من الdashboard، يُوقّع، ثم OTA للجهاز)
from vde.api import Detector, Detection

class FishCount(Detector):
    key = "fish_count"
    kind = "edge_model"
    triggers = ["motion"]          # يُشغَّل عند حركة في كاميرا الحوض

    def setup(self, ctx):
        self.det = ctx.onnx(ctx.model_path)     # OnnxDetector مشترك (YOLOv8-ONNX)
        self.min_count = int(ctx.config.get("min_count", 20))

    def on_event(self, ev, ctx):
        snap = ctx.snapshot(ev["camera"])
        if not snap:
            return None
        hits = self.det.detect(snap, conf=0.35)          # أصناف النموذج من ONNX metadata
        fish = [h for h in hits if h[0] == "fish"]
        if len(fish) < self.min_count:                    # نفوق/تجمّع غير طبيعي مثلاً
            return Detection(flagged=True, score=len(fish),
                             note=f"عدد الأسماك المرصود {len(fish)} < {self.min_count}",
                             label="fish_count", snapshot=snap)
        return None
```

**الدورة كاملة (كلها عن بُعد):**
1. درّب YOLOv8 على داتاست أسماك → صدّر ONNX (`veedon-fish-v1.onnx`، الأصناف داخل الـmetadata).
2. اكتب `detector.py` أعلاه، وارفع الاثنين من الdashboard.
3. السحابة **توقّع** الحزمة وتضيفها للسجل (`fish_count-v1.vmod`).
4. فعّل `fish_count` على كاميرا الحوض (config: `{"min_count": 20}`) → `modules_version` يرتفع.
5. الجهاز يسحب الحزمة → **يتحقق التوقيع** → يفكّها → **تحميل ساخن** → جاهزة خلال ثوانٍ **بلا إعادة تشغيل**.
6. حركة في الحوض → MQTT → المحرك يشغّل `FishCount.on_event` → استدلال ONNX → حدث + صورة → الdashboard.

نفس المسار يخدم أي كشف مخصّص: عيوب منتج، دواجن نافقة، سلوك محدّد… **كود + نموذج → يعمل فوراً.**

---

## 8. علاقته بالموجود (نعيد استخدام لا نبني من الصفر)

| موجود الآن | دوره في VDE |
|---|---|
| `analyzer.OnnxDetector` | يُستدعى عبر `ctx.onnx()` — الاستدلال المحلي |
| `/api/device/analyze` + `vlm.py` | يُستدعى عبر `ctx.vlm()` — التحليل السحابي |
| سجل النماذج `/models/{name}` | يتوسّع لسجل موديولات `/modules/{key}/{version}` (كود+نموذج+توقيع) |
| بوابة الجهاز (بصمة/اعتماد) | نفسها تحرس تسليم الموديولات |
| `config_version` + الاستطلاع | يضاف `modules_version` بنفس النمط |
| كتالوج المزايا `event_types` + `EdgeDevicePanel` | يتوسّع لإدارة الموديولات |
| قواعد/edge_model/vlm الحالية في `agent.py` | **تُعاد هيكلتها كموديولات مدمجة** (built-in Detectors) — توحيد كامل |

**التحوّل:** المنطق المتناثر حالياً (`check_edge_models`/`check_scheduled`/القواعد) يصير موديولات
مدمجة تحت نفس الواجهة؛ وتفعيل MQviaT في Frigate يحوّل التشغيل من استطلاع `/api/events` إلى
مدفوع بالأحداث (أدقّ وأخفّ).

---

## 9. خطة التنفيذ على مراحل

- **P1 — النواة:** ✅ **مبني ومُختبر** — `edge/vde/`: `api.py` (Detector/Context/Detection)،
  `engine.py` (توزيع MQTT + عمّال بثّ + جدولة + عزل/تعطيل تلقائي)، `builtins.py`
  (rule/edge_model/vlm)، تحميل ساخن (`loader.load_detector_class`). الوضعان يعملان.
- **P2 — سجل الموديولات OTA:** ✅ **مبني ومُختبر** — توقيع Ed25519 (`signing.py`)، حزمة
  `.vmod` موقّعة (`loader.pack/verify/install`)، عميل OTA (`registry_client.py`)، وبوابة
  السحابة (`cloud/api/app/modules.py`): `/api/device/modules[/{key}/{ver}]` + `modules_version`.
  **تحقّق تطابق التوقيع سحابة↔حافة + رفض حقن الكود/المفتاح الخاطئ.**
- **P3 — إدارة الdashboard:** ✅ **مبني** — رفع أدمن موقّع (`POST /api/admin/modules`) +
  صفحة `/modules` (رفع كود+نموذج، قائمة المنشور). التفعيل لكل كاميرا عبر كتالوج المزايا
  الحالي؛ الإصدارات/التراجع: البنية جاهزة (نسخ متعددة مخزّنة).
- **P4 — التصلّب:** ✅ **كود:** موديولات موقّعة فقط + عزل `try/except` لكل استدعاء + تعطيل
  تلقائي بعد أخطاء متتالية + الوصول الشبكي عبر `Context` فقط (لا شبكة مباشرة للموديول).
  ✅ **OTA حقلي كامل:** عميل `ModuleOTA` مدموج في الوكيل + نقطة `/api/device/module_pubkey`
  + `install.sh` يزوّد المفتاح العام → **تحقّق التوقيع قبل التشغيل** (مُثبت: provision→رفع
  موقّع→تنزيل→تحقّق + رفض حقن الكود). ✅ **تصلّب النشر:** compose فيه `no-new-privileges`
  + حدود CPU/ذاكرة + mosquitto + بلا مفتاح عام = لا تحميل (fail-safe).
  🔜 **يتبقّى:** docker-socket-proxy (بدل وصول السوكِت الكامل) + مراقبة صحة موسّعة.

### ما بُني فعلاً (كود مُختبر)
`edge/vde/`: `api.py · engine.py · builtins.py · loader.py · signing.py · registry_client.py`
+ `samples/fish_count/` + `test_vde.py` (P1 الوضعان/العزل، P2 التوقيع/التحميل الساخن، OTA).
السحابة: `cloud/api/app/modules.py` (+CORS). اللوحة: `pages/Modules.tsx`. **كل الفحوص تمرّ ✅.**

### ✅ الدمج في الوكيل + MQTT حي (مُختبر)
- **`edge/agent/vde_runner.py`**: VDE يملك `edge_model + vlm + موديولات OTA`؛ القواعد تبقى في الوكيل.
- **`agent.py` مُدمج**: تفعيل MQTT في إعداد Frigate + بدء المحرك (خيطا MQTT/جدولة) + `vde_sync`
  بعد كل تغيير إعداد؛ حُذف `check_edge_models`/فرع vlm (استبدلهما المحرك).
- **اختبار MQTT حيّ** (وسيط mosquitto حقيقي): نشر `frigate/<cam>/motion` + `frigate/events`
  (label=person) → المحرك وزّع → كشف ONNX أنتج حدثين. **مُثبت end-to-end.**
- النشر: `docker-compose.agent.yml` أضيف له **mosquitto** + سياق بناء يشمل `vde` +
  `paho-mqtt`/`cryptography` في `Dockerfile`.

### ✅✅ الدورة الكاملة على Frigate حقيقي (2026-07-05) — مُثبتة
محرك Frigate الفعلي (`veedon-ai/edge-engine`) يشغّل فيديو أشخاص، يكشف **person داخل منطقة
`restricted`**، وينشر أحداثاً حقيقية على `frigate/events` عبر mosquitto. VDE اشترك على البثّ
الحقيقي، ووزّع على `object:person` و`zone:restricted`، فسحب **اللقطة الحقيقية من Frigate**
(31KB) وشغّل **استدلال COCO-ONNX فعلياً** → كشف `person` بثقة **0.82/0.86/0.77**، وأطلقت
قاعدة المنطقة. **حدث Frigate → MQTT → VDE → ONNX حقيقي على لقطة حقيقية — الدورة كاملة على عتاد حقيقي.**

---

## 10. المخاطر

| الخطر | التخفيف |
|---|---|
| **تشغيل كود مرفوع (RCE)** | توقيع Ed25519 إجباري + اعتماد أدمن + سجل معتمد فقط + المفتاح العام مضمّن في الصورة |
| موديول يتعطّل/يستهلك موارد | عزل `try/except` لكل استدعاء + تعطيل تلقائي بعد أخطاء متتالية + حدّ CPU/زمن |
| تعارض اعتماديات موديول | `requires` محدود ومُدقّق؛ الأساس (onnxruntime/numpy) مشحون مع المحرك |
| تسرّب ذاكرة من التحميل الساخن المتكرر | اسم نسخة فريد + إزالة المرجع القديم + مراقبة RSS |
| فيضان أحداث MQTT | debounce + cooldown لكل (موديول، كاميرا) |
| عبء فكّ الترميز المستمر (الوضع البثّي) | مصدر واحد go2rtc مُعاد استخدامه + `fps` منخفض + تفعيل بثّي انتقائي لكاميرات محدّدة + حدّ عدد العمّال |
| خروج شبكي غير مرغوب من موديول | allowlist: الوصول الشبكي عبر `ctx` فقط (بوابة السحابة)، لا شبكة مباشرة |

---

**الخلاصة:** VDE يحوّل صندوق الحافة من "مشغّل Frigate ثابت" إلى **منصّة كشف قابلة للبرمجة عن بُعد** —
أضف كوداً ونموذجاً، يُوقّع ويُعتمد، يُحمَّل ساخناً، ويعمل فوراً. يسدّ فجوة Frigate في السلوكي والمخصّص،
ويوحّد كل الكواشف تحت واجهة واحدة تُدار بالكامل من الdashboard.
