---
read_when:
    - تغيير دورة حياة الموافقة على التنفيذ أو Plugin، أو التخزين، أو البروتوكول، أو التخويل
    - إضافة روابط الموافقة أو عناصر التحكم الأصلية بالموافقة إلى قناة
    - عرض موافقات الجلسات الفرعية في واجهات الجلسة الأم أو المنسّق
summary: تصميم موافقات دائمة قابلة للربط العميق عبر واجهة التحكم والتطبيقات الأصلية والقنوات والجلسات الأصلية
title: موافقات المشغّل متعددة الواجهات
x-i18n:
    generated_at: "2026-07-16T15:01:32Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 32
    provider: openai
    source_hash: 9defdaada1911df1184f64429e1787c4881e735c433d6dbc30a5946e11cc7cce
    source_path: refactor/operator-approvals.md
    workflow: 16
---

# موافقات المشغّل متعددة الواجهات

يتتبع هذا التصميم [#103505](https://github.com/openclaw/openclaw/issues/103505). وهو يستبدل صلاحية الموافقة المحلية للعملية بدورة حياة واحدة مملوكة لـ Gateway ومدعومة بـ SQLite. تحصل كل موافقة على تنفيذ أو Plugin/أداة مملوكة لـ Gateway على معرّف ثابت واحد، ومسار Control UI واحد مصادق عليه، وحسم ذري تكون فيه الغلبة لأول إجابة، وإسقاطات خاصة بالمشغّل إلى تدفقات جلسة المصدر والجلسات السلف.

تتعايش الإجراءات المضمّنة والروابط العميقة. ولا يوجد مفتاح تبديل لوضع الموافقة.

## الأهداف

- كائن موافقة دائم واحد لبوابات التنفيذ وPlugin/الأداة.
- مسار `${controlUiBasePath}/approve/{approvalId}` ثابت.
- الحسم من أي Control UI أو تطبيق أصلي أو واجهة قناة مصرّح بها.
- سلوك ذري تكون فيه الغلبة لأول إجابة عبر الواجهات المتزامنة.
- إعادات المحاولة المتطابقة خاملة الأثر؛ ولا يمكن للإجابات المتأخرة المتعارضة أن تستبدل الإجابة الفائزة.
- يؤدي انتهاء المهلة، والأحكام الموثوقة المشوّهة، والمسارات المفقودة، والإلغاء، وإعادة التشغيل إلى الرفض الآمن.
- تصل أحداث الطلب والانتهاء إلى جلسة المصدر وجميع مالكي الجلسات الأصل/المنسّق ذوي الصلة.
- تتلقى القنوات إجراءات موافقة وتنقّل ذات أنواع محددة؛ وتظل بيانات استدعاء النقل خاصة بالقناة.
- تظل أساليب التنفيذ/Plugin الحالية في Gateway متوافقة بينما تتقارب تطبيقاتها على خدمة واحدة.

## غير الأهداف

- استمرار تنفيذ الأداة المحظور نفسه أو استئنافه عبر إعادة تشغيل Gateway.
- جعل معرّف الموافقة أو عنوان URL بيانات اعتماد لحاملها.
- إلحاق مطالبات الموافقة بالنصوص المرئية للنموذج أو إيقاظ الوكلاء الأصل.
- نقل سياسة الموافقة أو أوامر المنتج أو تخويل المراجعين إلى Plugins القنوات.
- استنساخ حالة الموافقة لكل قناة أو جهاز أو جلسة سلف.
- إعادة تصميم قوائم السماح بالتنفيذ، أو تركيب سياسة Plugin، أو استمرارية `allow-always` إلا حيث يلزم لجعل النتائج النهائية غير ملتبسة.
- إتاحة الوصول عن بُعد في المرحلة الأولى إلى TUI مضمّن يعمل بلا Gateway. ويظل محليًا فقط ويجب أن يرفض بأمان عند عدم وجود مراجع.

## خط الأساس قبل الطرح وخريطة الأدلة

يسجّل هذا الجدول حالة التنفيذ عند فتح #103505. تتتبع أقسام الطرح أدناه سجلًا دائمًا، وإجراءات ذات أنواع محددة، وصفحة رابط عميق، وزيادات العميل الأصلي المبنية فوق خط الأساس هذا.

| الواجهة           | نقطة الدخول والمالك في خط الأساس                                                                                                                                  | السلوك والفجوة في خط الأساس                                                                                                                                                                    |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| تنفيذ الوكيل        | `src/agents/bash-tools.exec-approval-request.ts`، `src/agents/bash-tools.exec-host-shared.ts`                                                                   | يمنع تسجيل `exec.approval.*` ثنائي المرحلة حالة تسابق مبكرة في `/approve`، لكن لا يزال من الممكن أن يتحول انتهاء المهلة إلى سماح عبر `askFallback`.                                                        |
| بوابة أداة Plugin  | `src/agents/agent-tools.before-tool-call.ts`                                                                                                                    | تطلب `plugin.approval.*`؛ ويمكن لـ `timeoutBehavior: "allow"` الموافقة على بوابة انتهت مهلتها. للوضع المضمّن صلاحية منفصلة محلية للعملية في `src/infra/embedded-plugin-approval-broker.ts`. |
| بوابة عقدة Plugin  | `src/gateway/node-invoke-plugin-policy.ts`                                                                                                                      | تُنشئ وتبث مباشرة عبر مدير Plugin، مما يكرر جزءًا من دورة حياة أسلوب الخادم.                                                                                 |
| صلاحية Gateway | `src/gateway/server-aux-handlers.ts`، `src/gateway/exec-approval-manager.ts`، `src/gateway/server-methods/approval-shared.ts`                                   | يستخدم مديرا التنفيذ وPlugin المنفصلان خرائط محلية للعملية. تبقى الإدخالات النهائية لمدة 15 ثانية. ولا تكون الغلبة لأول إجابة إلا داخل عملية واحدة.                                          |
| بروتوكول Gateway  | `packages/gateway-protocol/src/schema/exec-approvals.ts`، `packages/gateway-protocol/src/schema/plugin-approvals.ts`، `src/gateway/methods/core-descriptors.ts` | للتنفيذ `get` للحالات المعلقة فقط؛ وليس لـ Plugin أي `get`؛ ولا توجد عملية بحث نهائية مستقلة عن النوع للرابط العميق.                                                                                   |
| التسليم          | `src/infra/exec-approval-channel-runtime.ts`، `src/infra/approval-native-runtime.ts`، `src/infra/approval-handler-runtime.ts`                                   | يدعم توجيه المصدر، والرسائل المباشرة للموافقين، وإعادة تشغيل المعلّق، والمعالجات الأصلية، والتنظيف النهائي داخل العملية. تضيف متابعة منفصلة تسوية نهائية دائمة.                          |
| الإجراءات المحمولة  | `src/interactive/payload.ts`، `src/plugin-sdk/interactive-runtime.ts`، `src/plugin-sdk/approval-reply-runtime.ts`                                               | أزرار الموافقة هي إجراءات أوامر تحتوي على `/approve ...`؛ وأهداف URL وWeb App هي حقول أزرار بلا أنواع محددة.                                                                           |
| Telegram          | `extensions/telegram/src/approval-handler.runtime.ts`، `extensions/telegram/src/button-types.ts`                                                                | يحلل العارض نص الأمر للتعرف على دلالات الموافقة قبل إنتاج بيانات استدعاء خاصة.                                                                                     |
| Control UI        | `ui/src/app/exec-approval.ts`، `ui/src/app/overlays.ts`، `ui/src/components/exec-approval.ts`                                                                   | واجهة الموافقة نافذة منبثقة عامة. يستخدم `ui/src/app-route-paths.ts` و`ui/src/app-routes.ts` مسارات مطابقة تمامًا ويعيدان كتابة المسارات غير المعروفة إلى الدردشة.                                                    |
| ملكية الجلسة | `src/agents/subagent-registry.types.ts`، `src/agents/subagent-registry-read.ts`، `src/config/sessions/types.ts`                                                 | توجد ملكية وحدة التحكم، ومقدم الطلب، والأصل الصريح، وملكية الإنشاء القديمة، لكن أحداث الموافقة لا تُسقط إلى تدفقات تلك الجلسات.                                                    |
| الحالة المشتركة      | `src/state/openclaw-state-schema.sql`، `src/state/openclaw-state-db.ts`                                                                                         | تدعم المعاملات الفورية الحالية والتحديثات الشرطية في Kysely عملية مقارنة وضبط دائمة في `state/openclaw.sqlite`.                                                                   |

تشمل الاختبارات الحالية التمثيلية `src/gateway/exec-approval-manager.test.ts`، و`src/gateway/server-methods/approval-shared.test.ts`، و`src/agents/bash-tools.exec-gateway-approval.e2e.test.ts`، و`extensions/telegram/src/approval-handler.runtime.test.ts`، و`ui/src/e2e/approval-flow.e2e.test.ts`.

تظل حزمة SDK الخاصة بـ Plugin الحد الوحيد للقناة/Plugin. يجب تصدير تغييرات وقت تشغيل الموافقة والعرض عبر المسارين الفرعيين الحاليين `src/plugin-sdk/approval-*.ts` و`src/plugin-sdk/interactive-runtime.ts`؛ ويجب ألا تستورد شيفرة إنتاج Plugin الأجزاء الداخلية لـ Gateway.

## الأعمال السابقة

يوفر Omnigent دلالات مفيدة لتجربة المستخدم والإخفاق:

- [`approval.py`](https://github.com/omnigent-ai/omnigent/blob/46e3cd9754c3b8567f7b09f4d19b6249dabe0e80/omnigent/runtime/policies/approval.py) يعلّق ASK، ويطبق مهلًا زمنية لكل سياسة، ولا يتعامل إلا مع القبول المطابق تمامًا بوصفه موافقة.
- [`sessions.py`](https://github.com/omnigent-ai/omnigent/blob/46e3cd9754c3b8567f7b09f4d19b6249dabe0e80/omnigent/server/routes/sessions.py) يحتوي على بوابة حزام الاختبار الأصلي من جانب الخادم وإسقاط الطلب/الحسم إلى الجلسات السلف.
- [`ApprovePage.tsx`](https://github.com/omnigent-ai/omnigent/blob/46e3cd9754c3b8567f7b09f4d19b6249dabe0e80/web/src/pages/ApprovePage.tsx) يوفر صفحة الموافقة المستقلة للأجهزة المحمولة.

لا تنسخ ادعاء التخزين لديه دون تمحيص. فالحالة المعلقة النشطة الحالية محلية للعملية في [`_elicitation_registry.py`](https://github.com/omnigent-ai/omnigent/blob/46e3cd9754c3b8567f7b09f4d19b6249dabe0e80/omnigent/server/_elicitation_registry.py)، ويزيل [`e3b1f2a4c9d7_drop_pending_tool_calls_table.py`](https://github.com/omnigent-ai/omnigent/blob/46e3cd9754c3b8567f7b09f4d19b6249dabe0e80/omnigent/db/migrations/versions/e3b1f2a4c9d7_drop_pending_tool_calls_table.py) جدول الحالات المعلقة غير المستخدم. يتعمّد OpenClaw الذهاب أبعد من ذلك: SQLite هو المرجع الموثوق، وكل انتقال نهائي هو عملية مقارنة وضبط في قاعدة البيانات.

## البنية والملكية

يمتلك Gateway دورة الحياة:

1. يوفر وكيل أو خطاف Plugin أو سياسة عقدة طلبًا خاصًا بالنوع وربط تنفيذ محليًا للعملية.
2. يتحقق Gateway منه ويبني إسقاطًا منقحًا للمراجع.
3. تحسب خدمة الموافقة جمهور المصدر/المالك، وتدرج الصف المرجعي، ثم تسجّل المنتظر داخل العملية.
4. بعد الإدراج الدائم، ينشر Gateway أحداث الموافقة الحالية، وإسقاطات الجلسة، وإشعارات القنوات، والدفع الأصلي.
5. تحسم كل واجهة القرار عبر الخدمة نفسها.
6. تثبّت الخدمة انتقالًا نهائيًا واحدًا، وتوقظ منتظر وقت التشغيل، وتنشر الإسقاطات النهائية.
7. لا يؤدي فشل تسليم حدث إلى التراجع عن القرار المثبّت؛ ويستعيد العملاء الحالة عبر `approval.get` أو إعادة تشغيل القائمة.

حدود الملكية:

- `src/gateway/`: خدمة الموافقة، والتخويل، ومهايئات RPC، وإنشاء عناوين URL، ودورة حياة المنتظر، ونشر الأحداث.
- `src/state/`: المخطط المشترك وأنواع Kysely المولّدة.
- `src/infra/`: نماذج عرض الموافقة المنقحة وإنشاء العرض المحمول.
- `src/agents/`: طلب الحكم المرتجع وانتظاره وتطبيقه؛ دون استمرارية.
- `src/channels/` و`extensions/*`: عرض الإجراءات ذات الأنواع المحددة، وتخويل مستخدمي القناة، وترميز الاستدعاءات الخاصة، وتحديث عناصر التحكم المسلّمة.
- `src/plugin-sdk/`: عقود الموافقة والعرض العامة فقط.
- `ui/`: الصفحة المستقلة وعملاء قائمة الانتظار/النافذة المنبثقة الحاليون.

المنتظر داخل العملية هو آلية إشعار، وليس مرجع الصلاحية. يدرج التسجيل الصف ويثبت المنتظر تزامنيًا قبل نشر الطلب، بحيث لا يمكن لأي حاسم أن يتداخل بين هاتين الخطوتين. ويثبّت كل حاسم لاحق القرار عبر SQLite قبل إنهاء ذلك المنتظر.

## السجل الدائم

أضف جدول `operator_approvals` واحدًا إلى قاعدة بيانات الحالة المشتركة.

| العمود                                             | الغرض                                                                                                                                       |
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `approval_id`                                      | معرّف أساسي فريد عالميًا. احتفظ بمعرّفات التنفيذ الحالية ومعرّفات `plugin:` لتوافق البروتوكول، لكن لا تستنتج النوع مطلقًا من البادئة.      |
| `resolution_ref`                                   | محدِّد موقع فريد وكامل من نوع SHA-256 base64url لاستدعاءات النقل التي لا يمكنها حمل المعرّف الأساسي. وهو ليس تخويلًا ولا معرّف URL عامًا. |
| `kind`                                             | مميّز `exec \| plugin` مغلق.                                                                                                        |
| `status`                                           | حالة `pending \| allowed \| denied \| expired \| cancelled` مغلقة.                                                                          |
| `presentation_json`                                | إسقاط مراجِع متحقق منه وموسوم بالنوع. تظل طلبات وقت التشغيل الأولية وارتباطات الأوامر وحمولات الاستدعاء محلية للعملية.               |
| `source_agent_id`, `source_session_key`            | هوية المصدر ومرساة إسقاط الجلسة. مفتاح الجلسة دائم؛ أما UUID المتغير للجلسة فليس كذلك.                                          |
| `audience_session_keys_json`                       | مصفوفة JSON مرتبة ومزالة التكرارات تنتجها عملية اجتياز الملكية المحدودة باتساع أولًا. تستخدم أحداث الطلب والأحداث النهائية اللقطة نفسها. |
| `requested_by_device_id`, `requested_by_client_id` | بيانات وصفية دائمة لمقدّم الطلب/التدقيق. يبقى معرّف الاتصال في الذاكرة وليس كيانًا رئيسيًا عابرًا للأسطح.                                         |
| `reviewer_device_ids_json`                         | أجهزة مراجِعين مستهدفة صراحةً واختيارية، لا يوفّرها إلا وقت تشغيل الموافقة الموثوق.                                                  |
| `runtime_epoch`                                    | حقبة العملية المالكة للتنفيذ المتوقف؛ تُستخدم لإلغاء الصفوف اليتيمة بعد إعادة التشغيل.                                                     |
| `created_at_ms`, `expires_at_ms`, `updated_at_ms`  | التوقيت المرجعي.                                                                                                                         |
| `decision`                                         | قرار المستخدم الصريح عند وجوده.                                                                                                       |
| `terminal_reason`                                  | سبب مغلق مثل `user` أو `timeout` أو `malformed-verdict` أو `no-route` أو `run-aborted` أو `gateway-restart`.                                |
| `resolved_at_ms`, `resolver_kind`, `resolver_id`   | هوية الفائز والتدقيق المحتفظ بها على جانب الخادم. تحذف إسقاطات المراجِع معرّفات الحسم الأولية.                                           |
| `consumed_at_ms`, `consumed_by`                    | حاجز منفصل ضد إعادة التشغيل لـ `allow-once`؛ يجب ألا يؤدي الاستهلاك إلى محو القرار المسجل.                                                       |

الفهارس المطلوبة:

| الفهرس                                      | الغرض                                                                     |
| ------------------------------------------ | --------------------------------------------------------------------------- |
| `(resolution_ref)` فريد                  | رفض غموض `approval_id`/`resolution_ref` العابر للأعمدة أثناء الإدراج. |
| `(status, expires_at_ms)`                  | العثور على الموافقات المعلقة وتسوية المواعيد النهائية المرجعية.               |
| `(source_session_key, created_at_ms DESC)` | إعادة تشغيل الموافقات الحديثة لجلسة مصدر واحدة.                             |
| `(resolved_at_ms)`                         | إزالة الموافقات النهائية المحتفظ بها وفقًا لسياسة الاحتفاظ الثابتة.  |

مصفوفات الجمهور صغيرة ومحدودة. تحدد إعادة التشغيل المفلترة حسب الجلسة أولًا الصفوف المعلقة المرئية عبر Kysely، ثم تفك ترميز مصفوفات الجمهور المحدودة وترشحها في شيفرة التطبيق؛ ولا تستخدم مطابقة السلاسل أو استعلامات JSON ذات SQL الأولي.

احتفظ بالصفوف النهائية لمدة 30 يومًا، بما يتوافق مع مدة الاحتفاظ بتدقيق البيانات الوصفية في `src/audit/audit-event-store.ts`. الإزالة سياسة صيانة ثابتة وليست سطح إعداد جديدًا. قاعدة البيانات حالة خاصة محلية لمستوى التحكم، لكن يجب ألا تكشف واجهات API الخاصة بالمراجِعين مطلقًا الطلب المخزن كاملًا أو ارتباط وقت التشغيل.

## آلة الحالات والمقارنة والتعيين

هذه الانتقالات وحدها صالحة:

- `pending -> allowed`: ‏`allow-once` أو `allow-always` صريح.
- `pending -> denied`: رفض صريح، أو حكم نهائي موثوق لكنه مشوه، أو عدم وجود مسار تسليم.
- `pending -> expired`: بلوغ الموعد النهائي المرجعي.
- `pending -> cancelled`: إجهاض التشغيل، أو إيقاف تشغيل سليم، أو استرداد العناصر اليتيمة بعد إعادة التشغيل.

الحكم الفعلي لكل حالة نهائية غير مسموح بها هو الرفض.

تستخدم عملية الحسم معاملة SQLite فورية واحدة وتحديثًا شرطيًا عبر Kysely مكافئًا لما يلي:

```sql
UPDATE operator_approvals
SET status = ?, decision = ?, terminal_reason = ?, resolved_at_ms = ?
WHERE approval_id = ?
  AND status = 'pending'
  AND expires_at_ms > ?;
```

إذا لم يؤثر التحديث في أي صف، تقرأ المعاملة نفسها السجل:

- مفقود أو غير مخوّل: أرجع «غير موجود»؛ ولا تكشف وجوده.
- ما يزال معلقًا لكن الموعد النهائي قد حان: طبّق عليه المقارنة والتعيين إلى `expired`، ثم أرجع ذلك الصف النهائي.
- القرار المسجل نفسه: أرجع نجاحًا متكررًا آمنًا مع الفائز المسجل.
- قرار مختلف: تُرجع واجهة API الموحدة `applied: false` مع الفائز المسجل؛ وتحتفظ المحوّلات القديمة بـ `APPROVAL_ALREADY_RESOLVED` حيث يتطلب عقدها المنشور ذلك.
- أي حالة نهائية: لا تعدّلها مطلقًا.

يُعد `now == expires_at_ms` منتهي الصلاحية. وقت Gateway هو المرجع.

يستخدم تنفيذ `allow-once` عملية CAS ثانية على `consumed_at_ms IS NULL`، مرتبطة بسياق الأمر/تشغيل النظام الدقيق الموجود. يظل صف الموافقة سجل تدقيق بعد الاستهلاك.

يُرفض إدخال HTTP/RPC المشوه الذي لا يمكن مصادقته أو لا يحدد موافقة، من دون تعديل، ولا يمكنه منح الموافقة مطلقًا. أما الحكم النهائي المشوه الوارد من منظومة اختبار/منتظر موثوق لموافقة معروفة، فينقلها إلى `denied`.

## واجهة API لـ Gateway

أضف أساليب مراجِع لا تعتمد على النوع:

| الأسلوب                                    | العقد                                                                                                                                                                                                            |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `approval.get { id }`                     | يُرجع إسقاطًا مرئيًا معلقًا أو نهائيًا محتفظًا به.                                                                                                                                                          |
| `approval.resolve { id, kind, decision }` | يقبل المعرّف الأساسي أو مرجع النقل ثابت الحجم، ثم ينفذ التخويل، والتحقق من النوع والقرار المسموح، وتسوية الموعد النهائي، وعملية CAS النهائية. تحمل الاستجابة دائمًا المعرّف الأساسي. |

بعد نجاح عملية CAS، أرجع الإسقاط المودع فورًا. الأحداث القديمة ومعيدات توجيه القنوات ومنهيات الدفع عمليات متابعة بأفضل جهد؛ ويجب ألا يؤخر سطح بطيء أو فاشل الاستجابة الفائزة أو يتراجع عنها.

يبقى التحقق من الطلب الخاص بالنوع في `exec.approval.request` و`plugin.approval.request`. يتحول `exec.approval.get/list/waitDecision/resolve` و`plugin.approval.list/waitDecision/resolve` الحاليان إلى محوّلين عند حدود البروتوكول للخدمة الأساسية لأنهما واجهة API منشورة لـ Gateway. تنتقل الجهات الداخلية المستدعية إلى الخدمة في التغيير نفسه.

إسقاط المراجِع اتحاد موسوم:

```ts
type OperatorApproval = {
  id: string;
  status: OperatorApprovalStatus;
  presentation:
    | { kind: "exec"; commandText: string /* معاينة تنفيذ آمنة */ }
    | { kind: "plugin"; title: string; description: string /* معاينة Plugin آمنة */ };
  // حقول دورة الحياة المشتركة
};
```

المسار المستقر مشتق وليس مخزنًا. يُرجع `approval.get` القيمة `urlPath`؛ ويمكن أيضًا للأسطح التي تعرف أصلًا عامًا معتمدًا أن تتلقى `url` مطلقًا. تحذف لقطات المراجِع مفاتيح جلسات المصدر والجمهور. يحتفظ Gateway بمفاتيح التوجيه هذه على جانب الخادم لإسقاط `session.approval` المنفصل.

## الأحداث والإجراءات المحمولة

يحافظ PR 1 على أسماء الأحداث المنشورة وحمولاتها ومرشحات المستلمين الحالية على مستوى السجل:

- `exec.approval.requested`
- `exec.approval.resolved`
- `plugin.approval.requested`
- `plugin.approval.resolved`

يمكن أن تحتوي تلك الأحداث القديمة على طلب وقت التشغيل كاملًا، ولذلك يجب ألا تُرسل إلى كل عميل ضمن نطاق الموافقات. يضيف PR 5 حقول دورة حياة موسومة (`status` و`sourceSessionKey` و`urlPath` والبيانات الوصفية النهائية و`kind` على مستوى العرض) عبر إسقاط دورة الحياة المنقح بدلًا من توسيع نطاق تسليم الأحداث القديمة.

أضف حدث إسقاط `session.approval` ضمن نطاق الموافقة. انشر الحدث الأساسي مرة واحدة مع مفاتيح الجمهور المحفوظة؛ ويتلقى المشتركون في الجلسة المطابقة تمامًا الحدث نفسه لكل مفتاح مطابق:

- `sessionKey`: التدفق الذي يتلقى الإسقاط.
- `sourceSessionKey`: العنصر الفرعي/المصدر الذي أثار البوابة.
- `phase`: ‏`pending \| terminal`، مميّزًا مقابل حالة الموافقة.
- إسقاط `OperatorApproval` آمن واحد.

تشترك العملاء اختياريًا باستخدام `sessions.messages.subscribe { key, agentId?, includeApprovals: true }`. تضيف الاستجابة الناجحة `approvalReplay` تحتوي على ما يصل إلى 1,000 موافقة معلقة حاليًا لمفتاح التدفق المطابق تمامًا، ويكون العميل المشترك مخوّلًا أيضًا بمراجعتها على مستوى السجل. يجعل `truncated: false` إعادة التشغيل المفلترة مرجعية، وتستبدل العملاء التي تعيد الاتصال مجموعتها المحلية المعلقة بها؛ أما `truncated: true` فهو إشارة حمل زائد، ويجب أن تحتفظ العملاء بالإدخالات المحلية غير المرئية إلى أن يحسمها البحث الأساسي أو أحداث دورة الحياة اللاحقة. يرسل انتهاء صلاحية دائم يُكتشف لاحقًا أثناء إعادة التشغيل شواهد حذف نهائية فقط إلى الجماهير المشتركة والمخوّلة على مستوى السجل قبل إرجاع اللقطة الجديدة. يمكن لـ `operator.admin` الاشتراك اختياريًا مباشرةً؛ وتتطلب العملاء الأضيق هوية جهاز مقترنة و`operator.approvals` معًا. الاشتراك في الجلسة وحده لا يمنح مطلقًا إمكانية رؤية الموافقة.

سجّل الحدث تحت `operator.approvals` في `src/gateway/server-broadcast.ts`. الإسقاط رصدي: فهو لا يلحق مطلقًا صفوف النص المنسوخ، ولا يصدر `sessions.changed`، ولا يوقظ وكيلًا.

وسّع `MessagePresentationAction` في `src/interactive/payload.ts`:

```ts
type MessagePresentationAction =
  | { type: "command"; command: string }
  | { type: "callback"; value: string }
  | {
      type: "approval";
      approvalId: string;
      approvalKind: "exec" | "plugin";
      decision: ExecApprovalDecision;
    }
  | { type: "url"; url: string }
  | { type: "web-app"; url: string };
```

يبني النواة إجراءات قرار ذات أنواع محددة ورابط مراجعة منفصلًا عندما يتوفر أصل مطلق معتمد لـ Control UI. ترمّز القنوات إجراء الموافقة بتنسيق الاستدعاء الخاص بها وترسل الحل إلى الخدمة المعيارية. يستخدم الاستدعاء المعرّف المعياري الدقيق عندما يتسع له؛ وإلا فيستخدم ملخص التجزئة الكامل والفريد للصف `resolution_ref`. المرجع ليس سوى مفتاح بحث مضغوط: تظل مصادقة Gateway العادية، وتخويل السجل، والنوع الصريح، والتحقق من القرار المسموح به، وتسوية الموعد النهائي، وعملية CAS للإجابة الأولى سارية. يجب ألا تقتطع القنوات المعرّفات، أو تحل بادئات التجزئة، أو تحلل نص `/approve`، أو تستنتج النوع من بادئة المعرّف.

احتفظ بـ `button.url` و`button.webApp` وعناصر تحكم الموافقة المدعومة بالأوامر كمدخلات توافق مهملة لـ SDK الخاص بالـ plugin. طبّعها عند حد SDK؛ وانقل كل مستدعٍ داخلي مضمّن في طلب السحب نفسه. يظل `/approve {id} {decision}` بديلًا نصيًا وأمر CLI/دردشة، وليس العقد الدلالي للزر.

## Control UI

المسار هو `${basePath}/approve/{approvalId}`. المعرّف هو معامل المسار الوحيد؛ وتأتي هوية جلسة المصدر من السجل.

نظرًا إلى أن الموجّه الحالي لديه مسارات ثابتة دقيقة ويعيد كتابة المسارات غير المعروفة إلى الدردشة، فاكتشف هذا الرابط العميق في `ui/src/app/bootstrap.ts` قبل تطبيع المسار المعتاد. أعد استخدام إعداد Gateway/المصادقة المعتاد، لكن اعرض صفحة موافقة مستقلة خارج غلاف الشريط الجانبي والنافذة العامة المنبثقة.

يمتلك المستند Gateway الذي قدّم عنوان URL الخاص به. يتجاهل اتصاله الأولي تحديد Gateway البعيد المحفوظ للتطبيق الكامل من دون تغيير إعدادات ذلك التحديد أو نسخها؛ تبقى المصادقة وحدها محصورة في الجلسة مع Gateway المقدِّم. يمكن للمصادقة الأصلية الموثوقة أو تجاوز `gatewayUrl` المؤكد بشكل منفصل إعادة توجيهه. تحجز النواة نطاق `/approve` أحادي المقطع قبل مسارات HTTP الخاصة بالـ plugin واكتشاف الامتدادات الثابتة، بما في ذلك المعرّفات التي تنتهي بـ `.json` أو `.js`؛ وعند تعطيل تقديم Control UI، يفشل المسار المحجوز بشكل مغلق مع `404`. احتفظ بالصفحة في حزمة Control UI الرئيسية كي لا يؤدي فشل جزء محمّل عند الطلب إلى ترك قرار أمني عالقًا على مؤشر تحميل.

حالات الصفحة:

- جارٍ التحميل
- المصادقة مطلوبة
- قيد الانتظار
- جارٍ الحل
- تمت الموافقة أو الرفض هنا
- تم الحل في مكان آخر
- انتهت الصلاحية
- أُلغي
- محظور/غير موجود
- خطأ في الاتصال مع إعادة المحاولة

تستدعي الصفحة RPC الخاص بـ Gateway، وليس واجهة REST API ثانية بلا مصادقة. تؤدي إعادة تحميل المتصفح إلى إعادة قراءة الحالة الدائمة. ولا تضع أبدًا بيانات اعتماد Gateway في عنوان URL أو الاستعلام أو الجزء.

## التخويل والخصوصية

عنوان URL محدِّد موقع، وليس مصدر صلاحية. يتطلب الحل:

1. اتصال Gateway مصادقًا عليه؛
2. `operator.approvals` أو `operator.admin`؛
3. تخويل المراجع على مستوى السجل.

القواعد على مستوى السجل:

- يجوز لـ `operator.admin` المراجعة.
- تكون `reviewer_device_ids` مرجعية عند وجودها. لا يجوز أن يراجع سوى جهاز
  `operator.approvals` مقترن ومدرج؛ ولا يمتلك الجهاز الطالب وصولًا ضمنيًا
  ما لم يكن مدرجًا أيضًا.
- في غياب قائمة مراجعين صريحة، يجوز لجهاز
  `operator.approvals` المقترن الطالب مراجعة سجله الخاص.
- تحتفظ السجلات القديمة فعلًا التي لا تحتوي على ارتباط بطالب أو مراجع
  بإمكانية رؤية واسعة للأجهزة المقترنة كي لا تترك الترقيات أعمالًا معلقة سابقًا عالقة.
- يجوز لبيئات التشغيل الداخلية التي لا تستخدم أجهزة الحل، دون القراءة، عبر اتصال
  وقت تشغيل الموافقة محدود النطاق. لا تأتي هذه الصلاحية إلا من رمز وقت التشغيل
  الذي صادق عليه الخادم؛ ولا يمكن لحقول `approval.resolve` العامة
  إصدارها.
- تظل ملكية اتصال الطالب المباشر صالحة للمحوّلات القديمة؛ ولا
  تُستنتج أبدًا من تطابق اسم العميل.
- لا تغيّر عضوية الجمهور سوى العرض. ولا توسّع التخويل أبدًا.

لا يعرض `approval.get` سوى إسقاط المراجع المنقّح ويحذف مفاتيح توجيه المصدر/الجمهور الداخلية. يحمل حدث `session.approval` في طلب السحب 5 وجهته الواحدة `sessionKey` بالإضافة إلى `sourceSessionKey` بعد أن يطبّق Gateway لقطة الجمهور المحفوظة من جهة الخادم. تحتفظ أحداث التنفيذ/الـ plugin الحالية بحمولتها التاريخية ومستلميها المقيّدين إلى أن ينتقل المستهلكون. يظل الطلب القابل للتنفيذ وربط الأمر والاستمرار في المنتظر المحلي للعملية فقط. يحتوي الصف الدائم على العرض الآمن بالإضافة إلى بيانات دورة الحياة والتوجيه والتدقيق الوصفية؛ ولا يخزّن أبدًا قيم البيئة الخام أو بيانات الاعتماد أو ترويسات المصادقة أو بيانات استدعاء القناة.

## إسقاط الجمهور

احسب الجمهور مرة واحدة قبل الإدراج واحفظ اللقطة المرتبة. الملكية رسم بياني، وليست دائمًا سلسلة أصل واحدة: قد يكون للعنصر الفرعي متحكم حالي وطالب أصلي معًا، وقد يؤدي هذان المالكان إلى جذور مختلفة.

استخدم اجتيازًا حتميًا بالعرض أولًا:

1. ابدأ قائمة الانتظار بمفتاح جلسة المصدر.
2. لكل مفتاح يُزال من قائمة الانتظار، اقرأ أحدث صف في سجل الوكيل الفرعي وأضف حافتي الملكية المتميزتين إلى قائمة الانتظار بترتيب ثابت: `controllerSessionKey`، ثم `requesterSessionKey`.
3. عند وجود صف سجل صالح للاستخدام، لا تتبع أيضًا نسب إدخال الجلسة الذي قد يكون قديمًا بعد إعادة التوجيه. وإلا فأضف حافة البديل الحالية الوحيدة `parentSessionKey ?? spawnedBy` إلى قائمة الانتظار.
4. طبّع وأزل التكرار عند الإضافة إلى قائمة الانتظار كي يفوز المسار الأول والأقصر.
5. توقف عند 64 مفتاحًا فريدًا؛ ويحد سقف حجم الجمهور هذا أيضًا من عمق الاجتياز.

مصدر السجل هو `src/agents/subagent-registry-read.ts`؛ وحقول الملكية معرّفة في `src/agents/subagent-registry.types.ts`. حقول الرجوع الاحتياطي للجلسة معرّفة في `src/config/sessions/types.ts`.

تستخدم إسقاطات الطلب والحالة النهائية الجمهور المحفوظ نفسه حتى إذا تغيرت ملكية التركيز/المتحكم بينما تكون الموافقة قيد الانتظار. يضمن ذلك التنظيف النهائي لكل تدفق جلسة جمهور تلقى إسقاط الطلب. يستهدف الحل دائمًا معرّف موافقة المصدر؛ ولا تتلقى جلسات الجمهور أبدًا حالة موافقة مستنسخة. يظل تنظيف رسائل القناة المعاد توجيهها متابعة منفصلة لمحدِّد التسليم أدناه.

لا تكتب رسائل نصية للجلسة، أو تحقن مطالبات نظام، أو تبدأ أدوار المالك، أو تصدر `sessions.changed` لمجرد وجود موافقة.

## تقارب الأسطح التي تم التسليم إليها

تحتفظ معالجات الموافقة الأصلية بالفعل بإدخالات رسائلها المسلّمة مدة كافية لاستبدال عناصر التحكم النشطة أو سحبها. تتجاهل رسائل الموافقة العامة المعاد توجيهها حاليًا `MessageReceipt`، لذا قد يترك القرار على سطح آخر عناصر التحكم القديمة تبدو قيد الانتظار. تغلق متابعة منفصلة هذه الفجوة باستخدام جدول فرعي `operator_approval_deliveries` في قاعدة بيانات الحالة المشتركة.

يخزّن كل صف معرّف الموافقة، ومعرّف تسليم فريدًا، والقناة/الحساب/المسار الدقيق، ومحدِّد موقع رسالة خاصًا بالقناة ومحدودًا ومتحققًا منه وفق JSON، وطوابع التسليم الزمنية، وحالة الإنهاء. ولا يخزّن أبدًا بيانات الاستدعاء أو رموز القرار أو طلبات الموافقة الخام. تمتلك القناة ترميز محدِّد الموقع وتعديل الرسالة؛ وتمتلك النواة الحالة المعيارية، واختيار الهدف، وسياسة إعادة المحاولة، والنص النهائي البديل.

يتعامل تسجيل التسليم والحل النهائي بأمان مع حالة التسابق:

1. بعد أن يعيد إرسال معلق إيصالَه، أدرج محدِّد موقع التسليم واقرأ حالة الموافقة الأصلية في معاملة واحدة.
2. إذا كان الأصل نهائيًا بالفعل، فجدول الإنهاء الفوري بدلًا من ترك التسليم المتأخر معلقًا.
3. يجدول كل انتقال نهائي مُعتمد جميع صفوف التسليم غير المنتهية بصورة منفصلة؛ ولا تكون عمليات البث القابلة للإسقاط هي المشغّل.
4. يبلغ مُنهي القناة عن `replaced` أو `retired` أو `unsupported`. يمنع الاستبدال إرسال رسالة نهائية مكررة؛ ويرسل السحب المتابعة النهائية الحالية؛ ويعود عدم الدعم أو الفشل إلى البديل دون التراجع عن CAS الخاص بالموافقة.
5. تعيد عملية بدء التشغيل محاولة الموافقات النهائية ذات عمليات التسليم غير المكتملة، ما يجعل التنظيف قادرًا على تحمل إعادة تشغيل Gateway.

دورة حياة النقل هذه خطّاف اختياري لمحوّل التسليم، وليست عارضًا أو إجراء رسالة موجّهًا إلى النموذج. لا تملك رسائل QQ من شخص إلى شخص/المجموعة حاليًا واجهة API للتحرير أو الحذف أو مسح لوحة المفاتيح؛ ويظل ذلك المحوّل غير مدعوم ولا يمكنه سوى إظهار الحقيقة المعيارية بعد نقرة لاحقة إلى أن يكتسب النقل واجهة API للتعديل.

## دلالات إعادة التشغيل والمهلة والمسار

لا يعني الحفظ في SQLite استئناف التنفيذ. تظل روابط الأمر/الأداة في الذاكرة لأنها قد تحتوي على حقائق حساسة أمنيًا خاصة بوقت التشغيل وليست عقد مهمة قابلة للاستئناف.

عند بدء تشغيل Gateway:

- أنشئ حقبة وقت تشغيل جديدة؛
- انقل ذريًا الصفوف المعلقة من الحقب الأقدم إلى `cancelled` مع السبب `gateway-restart`؛
- احتفظ بالصفوف كي تشرح عناوين URL الخاصة بها ما حدث؛
- لا تنفذ أبدًا موافقة لاحقة مقابل ربط وقت تشغيل مفقود.

المؤقتات تحسينات للاستيقاظ. تُخزّن سلطة الموعد النهائي في `expires_at_ms`؛ وتجري القراءات وعمليات الانتظار والحل جميعها تسوية انتهاء الصلاحية.

السلوك الصارم النهائي:

- انتهاء المهلة -> `expired`، رفض؛
- لا يوجد مسار -> `denied`، رفض؛
- إحباط التشغيل -> `cancelled`، رفض؛
- حكم موثوق مشوه -> `denied`، رفض؛
- قرار سماح صريح ومسموح به فقط -> `allowed`.

لا يزال سلوك التنفيذ المُصدَر حاليًا يتعارض مع هذا العقد:

- قد يطبّق `src/agents/bash-tools.exec-host-shared.ts` القيمة `askFallback`.
- يوثّق `docs/tools/exec-approvals.md` و`docs/cli/approvals.md` ذلك السطح.

تفشل موافقات الـ plugin الآن بشكل مغلق عند انتهاء المهلة والأحكام المشوهة؛ ويظل الحقل القديم
`timeoutBehavior` مقبولًا لكنه متجاهل. يجب أن تحدّث متابعة
الدلالات الصارمة للتنفيذ الشفرة والأنواع والمستندات والاختبارات وسجل التغييرات معًا، مع
مراجعة صريحة من المالك/الأمن. يجوز لـ `askFallback` الاستمرار في وصف
اختيار السياسة السابق للبوابة أثناء الانتقال، لكنه يجب ألا يحوّل انتهاء مهلة
سجل معلق تم إنشاؤه إلى موافقة.

## خطة التوافق

- بروتوكول Gateway إضافي؛ لا زيادة في إصدار البروتوكول.
- حافظ على أساليب وأحداث التنفيذ/الـ plugin الحالية عند الحد الخارجي.
- احتفظ بالمعرّفات الحالية، بما فيها بادئات `plugin:`، لكن توقف عن استخدام البادئات كمعلومات عن النوع.
- احتفظ بسلوك أمر النص `/approve`.
- احتفظ بحقول URL/Web App القديمة للأزرار وإجراءات الأوامر كمدخلات توافق لـ SDK الخاص بالـ plugin؛ ويكون خرج النواة الجديد ذا أنواع محددة.
- انقل جميع القنوات المضمّنة والمستدعين الداخليين في تغيير الإجراءات ذات الأنواع المحددة نفسه.
- أضف إدخالًا في سجل التغييرات لعنوان URL/الصفحة الجديدين ولتغيير سلوك المهلة اللاحق.
- لا تضف إعدادًا لوضع الاستدراج.

## الطرح

### طلب السحب 1: دورة حياة دائمة

- ملاحظة التصميم هذه.
- مخطط SQLite المشترك، وتوليد Kysely، والمخزن، والحذف بعد 30 يومًا.
- خدمة موافقة Gateway، وجسر منتظر وقت التشغيل، ومعالجة العناصر اليتيمة عند إعادة التشغيل.
- `approval.get/resolve` موحّد.
- محوّلات أساليب التنفيذ/الـ plugin.
- اختبارات فوز الإجابة الأولى، والتكرار الآمن، وانتهاء الصلاحية، والتخويل، والاستهلاك.
- لا تغيير في سلوك الواجهة أو القناة حتى الآن.

### طلب السحب 2: الإجراءات ذات الأنواع المحددة واستدعاءات القنوات

- إجراءات الموافقة وعناوين URL وتطبيق الويب ذات الأنواع المحددة.
- منشئات العرض الأساسية وصادرات SDK الخاصة بالـ Plugin.
- ترميز رد النداء الخاص بالنقل مع نوع مالك صريح.
- مراجع رد نداء دائمة وثابتة الحجم للمعرّفات الأساسية التي تتجاوز حدود النقل.
- ترحيل القنوات المضمّنة بعيدًا عن استنتاج نص الأمر ومعرّف الموافقة.
- الحقيقة الأساسية لأول إجابة على السطح الذي تم النقر عليه، وتحديثات طرفية أصلية نشطة وفق أفضل جهد؛ وتظل الإنهائية الدائمة لرسائل القنوات متابعة لاحقة.
- اختبارات SDK والقنوات المضمّنة.

### PR 3: رابط عميق لواجهة التحكم

- صفحة موافقة مستقلة ومصادق عليها، وتوجيه بدء تشغيل يراعي المسار الأساسي.
- الارتباط بالـ Gateway المقدِّم للخدمة دون تعديل الاختيار البعيد المحفوظ لدى المشغّل.
- نطاق أسماء HTTP للموافقة مملوك للنواة، بما في ذلك المعرّفات الشبيهة بالأصول.
- حمولة URL ينشئها Gateway، واستطلاع الحالة المعلّقة إلى حين توفير أحداث دورة الحياة.
- إثبات العرض بعرض الهاتف المحمول، وإعادة الاتصال، والإجابة المنافسة، وإعادة التحميل، والمسار المركّب.

### PR 4: العملاء الأصليون

- تستخدم أسطح المراجعة في iOS وAndroid القيمة `approval.get/resolve` مع مراعاة النوع؛ ويرحّل watchOS مطالبات المراجع وقراراته بأمان عبر iPhone المقترن.
- توفّر Watch قرارات التنفيذ التي يدعمها عقد الترحيل المدمج الخاص بها: السماح مرة واحدة والرفض.
- تحل الحقيقة الطرفية الأساسية لأول إجابة محل حالة محاولة القرار المحلية.
- تؤدي إقرارات الحل المفقودة أو الملتبسة إلى تجميد عناصر التحكم حتى إعادة قراءة الحالة الأساسية.
- تحتفظ مثيلات Gateway v4 السابقة التي تم إصدارها بمراجعة التنفيذ عبر رجوع ضيق إلى الطريقة القديمة؛ ويتطلب الاحتفاظ بالحالة الطرفية عبر الأسطح الطرق الموحّدة.
- تظل تحذيرات المراجع وسياق المالك مرئية عبر iPhone وWatch وAndroid.
- إثبات الوحدات الأصلية والبناء والمنصة.

### PR 5: نشر دورة الحياة إلى الأسلاف

- تسليم `session.approval` المعلّق/الطرفي من لقطة الجمهور المحفوظة في PR 1.
- اشتراك الجلسة المطابقة تمامًا، وإعادة التشغيل عند إعادة الاتصال، وشواهد القبور الطرفية دون تعديل السجل النصي أو إيقاظ الوكيل.
- تعمل استدعاءات دورة الحياة بعد الإدراج الدائم/عملية CAS ولا تصبح أبدًا جهة تخويل للموافقة.
- إثبات الوكيل الفرعي المتداخل وإعادة الاتصال.

### PR 6: سلوك الإغلاق الآمن

- ترحيل `node-invoke-plugin-policy.ts` ووسيط الـ Plugin المضمّن بعيدًا عن ازدواجية جهة التخويل.
- دلالات صارمة للمهلة الزمنية والتنسيق غير الصحيح وغياب المسار والربط واستهلاك السماح لمرة واحدة.
- إهمال إعدادات المهلة المتساهلة التي تم إصدارها دون العمل بها بعد أن يصبح الطلب معلّقًا.
- إثبات التنافس متعدد الأسطح وحقن الأعطال.

### متابعة: التنظيف الدائم للرسائل البعيدة

- حفظ محددات مواقع التسليم المُعاد توجيهه وإنهاء كل رسالة قناة مسلّمة بعد إعادة التشغيل.
- إبقاء دورة حياة النقل هذه منفصلة عن جهة التخويل الأساسية للموافقة وإجراءات العرض ذات الأنواع المحددة.

## الاختبارات

التغطية المركّزة المطلوبة:

- تحافظ إعادة فتح SQLite على الإسقاطات المعلّقة والطرفية.
- ينتج عن حالَّين متزامنين فائز واحد بالضبط في CAS.
- تنجح إعادة المحاولة بالقرار نفسه بصورة آمنة تكراريًا؛ وتعيد المحاولة المتعارضة الفائز المسجّل.
- لا يمكن للحل عند الموعد النهائي أو بعده أن يمنح الموافقة.
- يمكن استهلاك `allow-once` مرة واحدة بالضبط دون محو حالة التدقيق الطرفية.
- يلغي بدء التشغيل عصور وقت التشغيل الأقدم.
- لا يكشف البحث أو الحل غير المصرّح بهما عن وجود السجل.
- قائمة السماح الصريحة للمراجعين وسلوك `operator.approvals` المقترن العام.
- تشترك طرق التنفيذ والـ Plugin القديمة في المخزن نفسه.
- مخططات الطلب/القائمة/الجلب/الحل في Gateway وحمولات الأحداث الإضافية.
- تسوية الإجراءات ذات الأنواع المحددة، والعرض الاحتياطي، وصادرات SDK، وتبديلات القنوات المضمّنة.
- يحتوي ترميز رد النداء في Telegram على بيانات خاصة بالنقل ولا يتضمن استنتاجًا من سلسلة الأمر.
- الابن المباشر، ومالكو المتحكم/الطالب المتفرعون، والمالكون المتداخلون، وإعادة التعيين، والرجوع إلى حقل الجلسة، والدورة، والحد الأقصى لحجم الجمهور.
- مصفوفتا الجمهور المطلوب والطرفي متطابقتان.
- لا تتسبب إسقاطات المالك في تعديل السجل النصي أو إيقاظ الوكيل.
- يعمل مسار واجهة التحكم عند `/` وعند مسار أساسي مهيأ؛ وتعرض إعادة التحديث الحقيقة المعلّقة أو الطرفية.
- تُظهر الإجابات المتزامنة من واجهة التحكم وTelegram فائزًا واحدًا و«تم الحل في مكان آخر» لدى الخاسر.
- تحافظ معرّفات الموافقة الأصلية ومعرّفات مالك Gateway على بايتات UTF-8 الدقيقة عبر التوجيه والتسوية.
- يثبّت تفاوض عائلة RPC الأصلية عائلة أساسية أو قديمة واحدة لكل مسار Gateway مقبول، ولا يخفض الإصدار بصمت بعد الاستخدام.
- تجمّد إقرارات الحل الأصلية المفقودة الإجراءات حتى إعادة القراءة الأساسية؛ ولا يمكن لإعادة القراءة الفاشلة اختلاق فائز أو الإقرار بتحديث Watch.
- لا يُقبل ارتباط طلب لقطة Watch إلا لمالك Gateway المقترن المطابق تمامًا وبعد اكتمال إعادة قراءة أساسية على iPhone.
- إثبات مسار المستخدم عبر Testbox/Crabbox، بما في ذلك صفحة موافقة بعرض الهاتف المحمول، وتنظيف إجراءات Telegram، ورحلة ذهاب وإياب واحدة لمعلّق/حل/خاسر متأخر عبر Android وiPhone وWatch.

## قابلية الرصد

أصدِر سجلات انتقال منظّمة وخالية من المحتوى تتضمن معرّف الموافقة والنوع ومفتاح جلسة المصدر والحالة والسبب وزمن الاستجابة. لا تسجّل أبدًا المعاينة أو الربط الخام.

تتبّع:

- عدد الطلبات حسب النوع؛
- العدد الطرفي حسب النوع/الحالة/السبب؛
- مقياس الحالات المعلّقة؛
- زمن الاستجابة من الطلب إلى الحالة الطرفية؛
- نتائج سباق الحل: الفائز، وإعادة المحاولة الآمنة تكراريًا، والتعارض، وانتهاء الصلاحية؛
- عدد مسارات التسليم وحالات الرفض لغياب المسار؛
- إلغاءات الحالات اليتيمة عند بدء التشغيل؛
- حجم الجمهور.

يُعد الانتقال المثبّت نجاحًا حتى إذا فشل تسليم الحدث لاحقًا. يتعافى مشتركو دورة الحياة من خلال إعادة التشغيل في PR 5 والبحث الأساسي. وتظل الإنهائية الدائمة لرسائل القنوات هي المتابعة المنفصلة المذكورة أعلاه.

## قرارات مفتوحة

1. **أصل واجهة التحكم المتاح خارجيًا.** تحمل كل لقطة المسار النسبي المستقر `urlPath`. لا يجوز الإعلان عن URL مطلق إلا من موقع Tailscale Serve/Funnel مخزّن مؤقتًا بعد نجاح إتاحة Gateway؛ ولا تُعد `allowedOrigins` ولا ترويسات Host للطلب ولا `gateway.remote.url` ولا مرشحات الاسترجاع الحلقي/LAN المخصصة للعرض فقط أصولًا أساسية. يمكن لـTelegram استخدام غلاف Mini App المصادق عليه للاحتفاظ بمسار الموافقة خلال التمهيد. تظل الوكلاء العكسية الاعتباطية مقتصرة على المسارات النسبية حتى يتوفر عقد URL عام صريح خاضع لمراجعة منفصلة. لا تسمح أبدًا لقناة بتخمين الأصل.
2. **التحويل المتوافق إلى مهلة التنفيذ الصارمة.** تفشل مهل موافقة الـ Plugin الآن بصورة مغلقة آمنة، وقد تم إهمال `timeoutBehavior`. يحتاج عقد `askFallback` المتبقي الذي تم إصداره إلى مراجعة صريحة من المالك/الأمان، وسجل تغييرات، ووثائق، وقرار ترحيل/إهمال قبل أن يتوقف عن تخويل التنفيذ بعد انتهاء مهلة طلب معلّق.
3. **الوضع المضمّن دون Gateway.** التوصية: إبقاؤه محليًا فقط في البداية، ثم جعله عميلًا للخدمة الأساسية عند وجود Gateway. لا تعلن عن رابط عميق لا يستطيع أي خادم حله.
