Skip to content

HaramLite v0.2.9

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Sep 13:02
· 1 commit to 3ff9d95416cfacf6f401382f43ad1bfe110c1299 since this release

HaramLite 0.2.9 — «الحوافّ المكشوفة»

حالة هذه الوثيقة: منشورة. الإصدار v0.2.9 منشور عامّاً على GitHub بتاريخ
2026-09-25
(‏publishedAt = 2026-09-25T13:02:51Z · غير مسودّة · غير prerelease)، وهذا نصّ
النشرة نفسه
. والوسم v0.2.9 مؤشّر إلى الالتزام fca5882 (مقيس: كائن الوسم
8b454c0 ⇒ fca5882). والأرقام أدناه مقيسة محلياً على التزام ساكن
(git status فارغ قبل القياس وبعده) ولا تُنسب إلى CI إلا حيث نُصَّ على خلافه.

المصدر: git log main..0db0ade — ٥٥ التزاماً، والدمج من main = ada7b2c
(مقيس: git rev-list --count main..0db0ade = 55). كل بند أدناه مسنود ببصمة التزامه،
لا بذاكرة كاتب.

الإصدار 0.2.9 — موحَّد في package.json · src-tauri/tauri.conf.json · src-tauri/Cargo.toml · src-tauri/Cargo.lock (pnpm versions:check ⇒ ✓ · exit 0)
إضافة المتصفّح 1.1.9 — دورة نشر مستقلّة، لا تُوحَّد مع التطبيق (browser-extension/manifest.json)
رأس فرع التكامل 0db0ade
قاعدة النطاق main = ada7b2c (‏git merge-base main rehearsal/owner-test = ada7b2c)
عنصر النشر Release #36136806340 على 3ff9d95 ⇒ completed · success (بناء على عدّاءات GitHub). وخرج خمسة أصول: HaramLite_0.2.9_x64-setup.exe 196,944,377 بايت (187.8 MiB) · HaramLite_0.2.9_x64_en-US.msi 230,002,688 بايت (219.3 MiB) · SHA256SUMS.txt 194 · build-info.json 3,578 · sbom.cdx.json 422,856

١) ما أُصلح

١-١ · الإضافة: الوضع واللغة واللغتان معاً

المشكلة المقيسة: زرّ المشاهدة كان يرسل الطلب بلا وضع، فيقع التطبيق على افتراضيّه
(song) — أي أن الوضع المعروض في الواجهة لم يكن الوضع المُرسَل. وزرّ اللغة لم يكن
موجوداً أصلاً، وكانت اللغة تتبع navigator.languages وحدها.

  • 9d1c1e4 — feat(ext): 1.1.9 - the mode choice reaches the player, named bridge errors stop reading as empty replies, and the language has a visible switch
  • d2e3bd3 — fix(ext): the displayed mode is the mode that gets sent - the watch request carried none, so the app used its own default (song)
  • 2fabfba — زرّ المشاهدة يعود إلى المسار المؤقّت، والأوضاع الصريحة تُحفَظ، وقائمة سماح تمنع الإفراط في الإسقاط
  • a2d1656 — جواب جاسوس مستقلّ: الاتجاه عند التبديل الحيّ · مزلاج البدء · ومسربا HTML كانا يعيدان فتح Trusted Types
  • c800cff — انتظار القراءة الأولى قبل إرسال الوضع · متابعة تغييرات التبويب الآخر · بناء قائمة السياق مرّة واحدة · وطباعة الوضع المُرسَل
  • 0521402 — i18n(ext): لغة واحدة لكل نصّ — الجرد أولاً، ثم إعادة كتابة الإنجليزية، وقائمة سماح بالأسماء تمنع الرجوع
  • 2b1d8c2 — إعادة قياس صفوف المتصفّح بعد 1.1.9 (إبقاء المصفوفة صادقة)

القياس: pnpm ext:guard أخضر · pnpm ext:mutants (بوّابة سالبة) بلا ثقوب · صفوف المتصفّح الثلاثة بمكافآتها الرقمية وآثارها الملتزَمة في qa/browser-rows/.

١-٢ · البوت بلغتين + /lang

النصوص كانت عربية مكتوبة في telegram.rs بلا جدول ترجمة. الآن للبوت لغة، و/lang يبدّلها فعلاً.

  • c11fe88 — feat(telegram): the bot speaks English too, and /lang switches for real
  • 5d42327 — fix(telegram): «نصّا المالك الحيّان كانا نصوصي المرحليّة المتوازية» (تصحيح ذاتي مُعلَن)
  • 756d07a — قياس الحدّ الذي سمّاه الجاسوس على النصّ الخام: ليس موجوداً (نفي بالقياس لا بالرأي)

١-٣ · نافذة/شاشة الإعدادات

العطل المقيس: شاشة الإعدادات كانت شريطاً بطول ٦٧ بكسل داخل ترويسة مموّهة
(backdrop-filter ينشئ containing block فيحبس العنصر fixed)، ثم صارت نافذة ثانية، ثم
أُلغيت النافذة الثانية لصالح شاشة داخل النافذة في سبعة تبويبات.

  • db4e686 — feat(ui): an independent settings window opened from Rust, covered by the existing capability
  • 2085794 — feat(ui): the settings screen moves into the window, in seven tabs
  • a5fcb1e — refactor(ui): drop the second window, settings become an in-window screen
  • 03bb637 — الشاشة تأخذ الوضع من الرست، و CSS‑ي يكفّ عن إبطال hidden
  • 5e5ce0f — fix(ui): the settings screen was a 67px strip inside the blurred header (علّة backdrop-filter)
  • 10d8dfa — fix(settings): unnest the seven tab panels so every tab shows its own group
  • 5190f22 — test(settings): حكم على خريطة id → تبويب المعلَنة، لا على حضور DOM
  • 9a49379 — ci(layout): قياس شاشة الإعدادات لا القائمة المنسدلة وحدها
  • a00c8d5 / ba916f5 / 3f4e823 / 73d2b6b — انظر ١-٤

١-٤ · بوّابتا الحرّاس: الأسماء + مُفسَدات رست

الفجوة المقيسة: خطّ الأساس كان يقارن العدد وحده ⇒ اختبار يُحذف ويُوضع مكانه اختبار
تفاهي يمرّ
. والحارس الذي لا يسقط قط ليس حارساً.

  • a00c8d5 — feat(gate): compare the rust test *names*, not just the counts — جردُ أسماء في qa/rust-baselines.json؛ اسم غاب ⇒ يسقط البناء
  • ba916f5 — feat(qa): a baseline of test *names*, and the two measured holes as mutants — الثقبان المقيسان (نزع حارس واختبار تافه مكانه · اختبار صار #[ignore]) صارا مُفسَدَين
  • 3f4e823 — feat(qa): a negative gate for rust guards — نظير ext:mutants للجانب الرست
  • 73d2b6b — ci(gate): run the rust mutant gate in the existing job — موصولة بـCI (وإلا فهي نصيحة، قاعدة المشروع §١٠)
  • 8252a9d — تثبيت خطّ الأساس بعد إصلاح الجسر

القياس (على 2da7093): pnpm rust:gates ⇒ clippy ١٤/١٤ موضعاً فريداً · ٤١٨ ناجح (الأساس ٤١٨) · ٠ فاشل · ٥ مُهمَل · الجرد ٤١٨/٤١٨ اسماً والغائب صفر · exit 0 — قيس مرّتين بنفس النتيجة.

١-٥ · جرد التبويبات

الشاشة سبعة تبويبات، ولكل معرّف تبويبٌ معلَن في خريطة، والحارس يحكم على الخريطة لا على
حضور العنصر في DOM (وهو ما كان يمرّ على عنصر مخفيّ).

  • f2114a5, 3ce2baf, 9fa296c, 2471286, 96ab31e — فرع agent/settings2 (غير مدموج بعد؛ انظر §٣): حذف مسار #advanced-panel الميت · تثبيت الحارس على العنوان · حذف توصيل «المعاينة السريعة» ومستهلكيه · توسيع حارس المسارات الميتة إلى كل src
  • 5190f22 — التبويبات وid المعلَنة (مدمج)

١-٦ · حذف المسارات الميتة

  • 2471286 — refactor(queue): delete the dead quality-select wiring, and widen the dead-path guard to all of src
  • 9fa296c — refactor(main): delete the dead quick-preview DOM wiring, and drop its allowlist
  • f2114a5 — refactor(settings): delete the dead #advanced-panel path and guard against its return
  • 96ab31e — refactor(preview): delete the quick-preview setting and all of its consumers

حدّ أمانة: هذه الأربعة على agent/settings2 ولم تُدمج في فرع التكامل بعد (مقيس:
git rev-list --count 0db0ade..agent/settings2 = 5). فحذف المسارات الميتة ليس في 0.2.9
حتى تُدمج
— أُثبته هنا ولا أدّعي خلافه.

١-٧ · subcode وأعطال المحرّك

المشكلة: ردّ فشل بلا code يُقرأ في الإضافة «ردّ فارغ» فيضيع السبب.

  • e5ee662 — fix(slots,telegram,bridge): an occupancy connector, a t3 that measures its own resource, and engine subcodes
  • 02ef68a — test(bridge): a raw engine message is never a valid subcode
  • 824e211 — fix(bridge): carry watch and mode together so an explicit clip survives the request file

القياس: pnpm bridge:codes ⇒ ٧ رموز مُصدَرة في ١٧ موضع خطأ، ولكلٍّ مدخل وحالة ترجمة في
ملفَّي الإضافة (٢١ فحصاً · ٠ فاشل) · exit 0.

١-٨ · ترجمة رسائل الواجهة

العطل المقيس: رسائل يصوغها التطبيق (لا الإضافة) تُعرض عربية في واجهة إنجليزية،
وdirection="ltr" — وزيادة مفاتيح ترجمة لا تُصلحه لأن النصّ لا يأتي من الإضافة.

  • 3beea55 — fix(i18n): read the backend's error through its stable code, not the raw text
  • 58845e2 — fix(ui): every displayed backend error goes through errText
  • a273f08 — test(ui): no raw .error read escapes errText, and the codes match Rust both ways
  • 34e2dfa — test(ui): عدّ String(<catch binding>) عند مصرف العرض أيضاً — مُفسَد هو الذي وجد الثقب
  • 3ceb424 — ربط عيّنة الإلغاء بالنصّ الذي يبنيه separator.rs اليوم (وكان التعليق تاريخياً)

حدّ أمانة: هذه الخمسة على agent/texts2/agent/ui-texts ولم تُدمج (مقيس:
0db0ade..agent/texts2 = 6). ويبقى ط-١٢ مفتوحاً في docs/BACKLOG-0.3.md:353:
تفصيل المحرّك {e} يبقى عربياً داخل جملة إنجليزية.

١-٩ · إصلاح stderr والتنزيل

العطل المقيس (بلاغ المالك): تنزيل يفشل بعد ١٠٠٪ والرسالة والسجلّ يحملان تقدّماً فقط بلا
سطر الخطأ
. السبب الجذري بالقراءة: yt_dlp.rs كان يصرّف stderr إلى بالوعة تُرمى،
ويبني الذيل من stdout وحده — وyt-dlp يكتب ERROR: على stderr. ثم أُعيد إنتاجه بنصّه:
ERROR: unable to download video data: HTTP Error 403: Forbidden.

  • 68b7f5e — fix(ytdlp): stop discarding stderr — a failed download now carries its cause
  • b0d8f2d — feat(ytdlp): retry a transient download failure once — measured, logged, bounded
  • ba5a852 — fix(ytdlp): a download failure is no longer labelled "ملفات:" (file error)
  • 0e9da97 — fix(ytdlp): never retry a permanent failure, never spend a valid result
  • 36f2e62 — fix(qa): the rust baseline gate now compares tests_ignored too
  • 124ee13 — fix(ytdlp): judge the failure from its ERROR line, and rescue a valid slot on stall
  • 6e07746 — style(ytdlp): إزاحة توثيق لا تغيير كود

حدّ أمانة: السبعة على agent/x2-dl ولم تُدمج (مقيس: 0db0ade..agent/x2-dl = 13).
أي أن إصلاح stderr والتنزيل ليس في 0.2.9 حتى تُدمج.

١-١٠ · الإلغاء

العطل المقيس: الإلغاء من الواجهة كان يُسجَّل ERROR pipe: خطأ استدلال النموذج: تم إلغاء المعالجة من قبل المستخدم — أي إلغاء المستخدم يُقرأ عطلاً.

  • 705ae1b — fix(pipe): a user's cancel is not a failure — no ERROR line on the engine path
  • 1ca3b0d — fix(sep): type the stop in demix, and guard that the type is the one production builds
  • 1661a70 — test(pipe): إغلاق تفادي الحارس بالتعليق/التجزئة، وحراسة مساري المحرّك والأداة
  • 438ac78 — test(sep): حصر الحارس النصّي على متن demix، وجعل الحارس الحيّ غير قابل للتخطّي الصامت
  • 9375383 — إيقاف الحارس عن رفض الكود الصحيح، ومنع الحارس الحيّ من المرور بلا تنفيذ
  • fb9f250 — علم المشاهدة يعود إلى المسار المؤقّت
  • b348c38 — test(layout): الحارس يقيس الحالة التي يفتحها المستخدم فعلاً (agent/x4-cancel، غير مدموج: 0db0ade..agent/x4-cancel = 1)

الحدّ المعلَن للإلغاء: نداء الاستدلال حاجب ولا يُقطع داخل العملية ⇒ نقلُه إلى عملية
عاملة بندٌ مسجَّل في 0.3 (ARCHIVE/0.2.9PLAN.md:333، القرار ٢).

١-١١ · SSRF — ⚠️ ليس من عمل 0.2.9

طلب المشرف إدراج SSRF في «ما أُصلح»، والقياس ينقض ذلك:

$ git diff --stat main..0db0ade -- src-tauri/src/yt_dlp.rs
(فارغ — 0 بايت)
$ git merge-base --is-ancestor 011c5eb main   ⇒  True

حارس المضيفات المحلية/الخاصة (validate_download_url · private_scope، وفيه التغطية
المُعلَنة 127.0.0.0/8 · 10/8 · 172.16/12 · 192.168/16 · 0.0.0.0 · ::1 · fc00::/7
· fe80::/10 · localhost، والصور المتكافئة [::ffff:127.0.0.1] و[::ffff:7f00:1]
و[::ffff:c0a8:105]) قائم في src-tauri/src/yt_dlp.rs:51 و:125، وأُدخل في 011c5eb
وهو سلفٌ لـmain
(main..011c5eb = 0). ⇒ صفر تغيير على yt_dlp.rs في نطاق 0.2.9،
ولا يجوز أن يُدرَج بنداً في نشرة 0.2.9. الموجود فيه اليوم اختباران: ٩ تأكيدات
(local_and_private_targets_are_rejected، :1537) و٢ تأكيدات
(malformed_urls_are_rejected_before_any_process_runs، :1639).


٢) ما لم يُقَس (حدود معلَنة — لا تُقرأ كإنجاز)

منقولة بالاسم من تقارير ARCHIVE/*-report.md؛ والقاعدة قاعدة المشروع §٤.

ما لم يُقَس التقرير · القسم · السطر
عرض تلغرام في عميل حقيقي — لا توكن بوت ولا شبكة خارجية؛ كل قياس «ما يصل السلك» على خادم Bot API وهمي محلّي عبر TcpListener حقيقي. ومحرفا الشريط ▰/▱ غير مُختبَرين على عميل حقيقي (احتمال تباعد غير أحاديّ لم يُقَس) x3-tg-report.md §٨ · سطر 269
التزامن الحيّ داخل الصفحة (المشغّل) — لا متصفّح ولا GPU ⇒ كل أحكام التزامن والتسريع بنيوية من قراءة الكود؛ التأكيد النهائي بجدول حكم بيد المالك review-0.2.9-cline.md §٦ · سطر 241
مقطع حقيقي فيه صمت — مسار clip نُفِّذ فعلاً على يوتيوب حقيقي وأنتج ملفاً كامل الطول وkept=[] لأن المقطع بلا صمت ≥٨٠٠ مللي؛ فالقصّ نفسه مقيس بـffmpeg (full=6.000s kept_sum=4.300s served=4.300s diff=0.0000s) ولم يُقَس مقطع حيّ فيه صمت docs/BACKLOG-0.3.md:349 (ط-٧)
VRAM بثلاث مهامّ متزامنة — «ثالثٌ يُتوقَّع أن يتجاوز البطاقة ولم يُقَس عمداً»؛ والقياسان المتزامنان 4734 MiB (12 ث · CUDA) و7943 MiB من 8192 (180 ث · DirectML · هامش 249 MiB) docs/BACKLOG-0.3.md:314 و:319-325
تثبيت MSI على جهاز نظيف — يحتاج Windows Sandbox/آلة المالك؛ ولا صفّ مصفوفة مملوء لـ«تشغيل فصل بـCUDA على تثبيت NSIS وحده» مقابل MSI docs/BACKLOG-0.3.md:143 (ن-٣) · 0.2.9PLAN.md:212 (الطبقة ب)
سلوك الإضافة محمَّلة من المانيفست — كروم ١٥٣ لا يحترم --load-extension؛ فكل قياس حيّ حقنٌ عبر CDP للملف المشحون. غير المقيس: تسجيل الخدمة الخلفية وربط المانيفست، ونقرٌ حقيقيّ على مدخل قائمة السياق، وchrome.storage الحقيقي x1-ext-report.md §٧ · سطر 545 (بنود ١–٣)
جودة الترجمة الإنجليزية تحتاج مراجعة بشرية — «الحارس يشترط الوجود والتكافؤ لا المعنى»، و«الترجمة الإنجليزية صحيحة لغوياً» لم يُتحقَّق منها ui-texts-report.md §٧ · سطر 147 (الفقرة ٤)
ما لم يُقَس في آخر جولات التنزيل — (أ) هل الـ403 على المقطع الأخير أم على الطلب الكامل؟ (ب) هل استُنفدت --retries/--fragment-retries؟ (ج) أن إعادةً واحدة كانت ستُنجح فشل المالك بعينه (المقيس: نفس الأمر نجح 18 من 19) (د) زمن انتظار التصريف (1000ms) على شبكة/قرص بطيء (هـ) أن الواجهة/تلغرام تتصرف صحيحاً بعد التغيير (لم يُشغَّل التطبيق) x2-dl-report.md §٥ · سطر 177 (بنود ٢–٦) · و§٨-٦ سطر 310 · و§٩-٤ سطر 429
لا جلسة WebView2 ولا لقطة للتطبيق العامل — حارس الهندسة يقود كروم بلا واجهة على dist/ عبر CDP: نفس DOM ونفس CSS، لا داخل WebView2؛ وعدد النوافذ=١ مقيس بنيوياً لا بتشغيل settings2-report.md §٦ · سطر 244 (بنود ١–٢)
حارس الهندسة يقرأ نصّاً عربياً مشحوناً لا الجدول (i18n table NOT readable from src/i18n.ts — measuring GEOMETRY ONLY) ⇒ أثر تبديل اللغة على عرض التسميات غير مقيس settings2-report.md §٦ · سطر 250 (بند ٣)
لا عدّاء CI — كل الأزمنة محلية، وكلفة البوّابة على windows-latest (بناء بارد) لم تُقَس gates-report.md §٥ · سطر 138
G4 لم يُغلق — الاختبار المعطَّل بـreturn داخل جسمه لا يراه الجرد، وrust:mutants يقيس ما يغطّيه سجلّ التحويلات وحده gates-report.md §٥ · سطر 138 · قاعدة المشروع:٣٤٣
ci.yml لم يُشغَّل على GitHub (لا دفع) ⇒ الخطوات الجديدة مقيسة محلياً فقط gates-report.md §٥ · سطر 138
ui:live لم تُقَس في هذه الدفعة — منفذ التنقيح 9222 لم يستمع رغم WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS='--remote-debugging-port=9222'، بطريقتين مختلفتين لحقن المتغيّر ($env: قبل Start-Process، وStart-Process -Environment) ⇒ لا صفحة على CDP ⇒ لا قياس حيّ للتطبيق العامل. والتطبيق أقلع فعلاً (نافذة + ١٣ عملية WebView2 + سجلّ إقلاع) لكن بلا منفذ. ولم تُقَس على ثنائي 0.2.8 القديم عمداً — فذلك يقيس غير ما نُسلِّم قياس هذه الدفعة · ARCHIVE/deliver-report.md §التقرير النهائي
release:verify --strict لا وجود له — العلم --strict غير معرَّف في check-release-artifact.cjs (يعطي exit 2: «وسيط غير معروف»). المقيس هو pnpm release:verify مجرّداً ⇒ exit 0 قياس هذه الدفعة
مصفوفة الاختبار اليدوي لم تُمَلأ (تحتاج ويندوز + بوتاً حقيقيّاً + هاتفاً) x3-tg-report.md §٨ · سطر 269
تذبذب مقيس في اختبار قائم — telegram::tests::t3_two_users_run_in_parallel_up_to_the_global_cap: سببُه التنازع على الجهاز لا المنتج (٤٢ تشغيلاً ساكناً ⇒ صفر فشل؛ وتحت تنازع حقيقي ١ فشل من ٣٦) ⇒ «صفر فاشل» ليس قابلاً لإعادة الإنتاج بثقة، ولم يُغلق x4-cancel-report.md §٧ · سطر 603 (بند ٨) · docs/BACKLOG-0.3.md:359 (ك-١)

٢.١) قياس بعد الدفع (لا يُقرأ كخضرة CI)

ما قِيس النتيجة
Quality gate على fca5882 failure (مقروء 2026-10-03 · التشغيل 36136136810). ⚠️ والخطوة الساقطة واحدة: Rust baseline gate ⇒ «✗ اختبارات فاشلة: 7 · ✗ اختبارات ناجحة: 440 < الأساس 447 · ✗ اختبارات غابت عن التشغيل: 7 من 447». والأسماء الغائبة السبعة موجودة في src-tauri/src على fca5882 نفسه (متحقَّق: git grep على الالتزام) ⇒ ليست اختبارات محذوفة بل لم تُشغَّل. ولم أُعالج العطب: القرار للمالك
Quality gate بعد تثبيت الأداة التشغيلان 37144671050 و37145722314: سطر clippy سقط من قائمة الفشل في الاثنين (مقيس بالعدّ: صفر مطابقة لـ«تحذيرات clippy») وكان 15 موضعاً فريداً > الأساس 14 قبل التثبيت ⇒ الرقم صار قابلاً لإعادة الإنتاج
نشر الموقع pages build and deployment على fca5882 ⇒ completed success · وعلى c0bf651 وa8747a6 ⇒ completed success أيضاً (التزاما تحديث الموقع، انظر §٥)
حماية الفرع الدفع أعلن Bypassed rule violations: Required status check "gate" is expected ⇒ الفحص المطلوب اسمه gate، وقد تجاوزه الدفع بصلاحية المالك. ولا خضرة CI مدّعاة: التشغيل جارٍ، ولم تُقرأ نتيجته
release.yml قابل للتشغيل بـworkflow_dispatch نعم — لا تعطيل مقصود: صفر if: false · صفر continue-on-error · صفر خطوة معلَّقة. يبني المثبِّت بـtauri-apps/tauri-action@v0 وينشر إصدار GitHub (releaseDraft: false · permissions: contents: write) ثم ينشر SHA256SUMS.txt·build-info.json·sbom.cdx.json ويرفع أصول الإصلاح الذاتي إلى assets-v1
أسرار التوقيع غير مطلوبة الآن: سطرا TAURI_SIGNING_* محذوفان عمداً (قرار 2026-09-05، لأن سلسلة فارغة تُفشل المفكِّك) وincludeUpdaterJson: false ⇒ يعمل بلا سرّ، ويكفيه GITHUB_TOKEN التلقائي
مدّته المتوقَّعة لم تُقَس على عدّاء. المرجعان: بناء Rust محليّ بذاكرة دافئة ٥د٠٥ث (مقيس هنا) · وسجلّ المالك ١٥–٢٠ دقيقة نجاحاً و١٣٥٫٥ فشلاً
شرطه الخارجي يحتاج assets-v1 قائماً: bin/yt-dlp.exe وmodels/ غير متتبَّعين في git، وfetch_redist.js --verify يتحقّق من بصماتها ⇒ بلا تلك الأصول يفشل التشغيل

للتشغيل من المتصفّح: Actions → Release → Run workflow (الفرع main) — GitHub يبنيه على عدّاءاته وجهازك مطفأ. وtagName: v__VERSION__ سيستهدف v0.2.9 (الإصدار غير موجود على GitHub بعد ⇒ سينشئه).


٢.٢) تصنيف اختبارات كان يُظنّ أنها «متذبذبة» — قِيس فتبيّن أنه نقص بيئة (2026-10-03)

كُتب بعد أن صار ممكناً تشغيل المجموعة محلياً: bin/ وmodels/ كانتا فارغتين في كل شجرة،
فجُمعت الموارد من target/ (‏vc_redist والنموذج) ومن ffmpeg-lgpl-staging (‏ffmpeg/ffprobe)،
وبُصمة كل ملف طابقت المثبَّتة في repair.rs (‏node scripts/fetch_redist.js --verify ⇒
«‏4 مكوّناً + vc_redist.x64.exe موجودة وبصماتها مطابقة»).

الفجوة المقيسة: ci.yml كان يقول إن اختبارات ffmpeg/ffprobe «تتخطّى نفسها عند غياب
الأدوات»
— وهذا غير صحيح لهذه المجموعة: yt_dlp لا يكتفي بثنائي yt-dlp، بل يحكم على
«خانة صالحة» عبر slot_usable ⇒ media::probe ⇒ ffprobe؛ وبلا ffprobe تسقط لا تتخطّى.
وbin/ في CI stubs فقط (‏ci.yml: تُنشأ ملفات .stub)، فالبوّابة كانت تسقط لنقص بيئة
ويُقرأ ذلك «عطب اختبارات».

# الاختبار الحكم الدليل المقيس
١ yt_dlp::tests::a_transient_failure_is_retried_once_and_succeeds نقص بيئة (‏ffprobe) — لا يُهمَل بلا ffprobe: سقط برسالة الحارس «yt-dlp نجح دون ملف ناتج صالح — أعد المحاولة» والنداءان وقعا · وبـffprobe: نجح
٢ yt_dlp::tests::a_transient_failure_with_a_valid_slot_spends_no_retry نقص بيئة سقط بـ«نداءات: 2» بدل ١ لأن الحكم على الخانة لم يجد ffprobe
٣ yt_dlp::tests::a_valid_slot_is_success_even_when_ytdlp_exits_nonzero نقص بيئة سقط · ونجح بالمجموعة كاملة
٤ yt_dlp::tests::a_stalled_download_with_a_valid_slot_is_rescued نقص بيئة سقط · ونجح بالمجموعة كاملة
٥ yt_dlp::tests::a_truncated_container_is_refused_while_a_complete_one_passes نقص بيئة سقط عند media::probe(&truncated) — الموضع نفسه يسمّي ffprobe
٦ telegram::tests::t4_a_user_with_four_files_does_not_block_another_user سليم محلياً · متذبذب على العدّاء ⚠️ نجح محلياً في كل قياس، وسقط على العدّاء في التشغيلات الثلاثة الأخيرة ⇒ ليس نقص بيئة بل حسّاسية توقيت
٧ telegram::tests::t9_the_intro_is_pinned_once_at_the_moment_the_bot_is_added سليم محلياً · متذبذب على العدّاء ⚠️ نجح محلياً في كل قياس · وسقط على العدّاء في التشغيلات الثلاثة الأخيرة
٨ slots::tests::a_free_slot_is_taken_without_waiting_and_a_taken_one_reports_its_wait سليم محلياً · متذبذب على العدّاء ⚠️ نجح محلياً · وسقط على العدّاء في تشغيل ونجح في آخر ⇒ المجموعة تتناوب (‏2 ثم 3) على الشجرة نفسها

وحدّ أمانة على هذا الجدول: الصفوف ٦–٨ ليست «سليمة» بلا قيد — نجاحها المحلي مقيس، وسقوطها
على العدّاء مقيس أيضاً
، وكلها حسّاسة للتوقيت والتزامن (‏Instant::now/sleep/TcpListener
ومهامّ متزامنة). ولم تُهمَل ولم تُقنَّع: هي الخطوة الحمراء الوحيدة الباقية، والقرار في شأنها
للمالك (تهدئة المهلة الداخلية؟ إعادة محاولة؟ أم إعلانها متذبذبة موثَّقةً؟) — ولا أُصلحها تخميناً
لأنني لا أستطيع إعادة إنتاج سقوطها محلياً
، و«إصلاح» لا يُقاس على العلّة لبب نفسه |

العلاج: ci.yml صار يجلب ffmpeg وffprobe مع النموذج في الخطوة المتحقِّقة نفسها
(‏--only=model,ffmpeg,ffprobe). ولم يُهمَل اختبار واحد ولم يُعدَّل خطّ الأساس، لأن القياس
أثبت أن الاختبارات سليمة والبيئة ناقصة.

والقياس الختامي محلياً (1.95.0 المثبَّتة · المجموعة كاملة): cargo test --lib ⇒
‏447 ناجحاً · ٠ فاشلاً · ٥ مُهمَلة — وهو رقم خطّ الأساس بعينه، و‏8/8 للثمانية أعلاه.

وأثر الإصلاح على العدّاء مقيس (ثلاثة تشغيلات بعد الدفع): ‏8 ساقطة ⇒ 2 ثم 3،
وffmpeg/ffprobe نُزّلا وتحقّقت بصمتاهما في السجلّ ⇒ خمسة اختبارات yt_dlp أُصلحت
فعلاً
، وسطر clippy غائب في كلها. والباقي ٢–٣ هي الصفوف ٦–٨ أعلاه.

وما جرى بعد ذلك (2026-10-03 · تكملة): أُصلح عطلان في الأداة والقياس، فقُلص العدد من ٨ إلى ٣:

  1. البوّابة صارت تسمّي الساقط: كانت تحفظ مخرَج cargo test ثم تطبع الملخّص وحده ⇒
    صفر سطر ... FAILED وصفر panicked at في أربعة سجلات — فالعدّاء لم يكن يتكلّم.
    الآن تطبع الاسم · الموضع · نصّ الفشل لكل ساقط. وأول ما نطقت به كان الجواب:
    t4 على telegram.rs:10909 بـ«لم تبدأ المهامّ الخمس: [1000, 1002]» — مهمّتان من خمس
    خلال ٦٠ ثانية كاملة
    ، وكان ذلك يُقرأ «عطب ترتيب» وهو مهلة.
    والقياس المحلي: خمس تشغيلات ⇒ ٥٫٢٥–٥٫٣٠ ث للمهمّات الخمس مع اكتمالها.
  2. وُسِّعت مهلتان مُقيَّدتان — والمهلة سقفُ فشل لا شرط نجاح، فالدعاوى كما هي:
    مهلة انتظار البدء ٦٠ ⇒ ٣٠٠ ث · ومهلة قراءة الخادم المزيّف (أحاديّ الخيط، وt4 معه
    خمسة عمّال) ٥ ⇒ ٦٠ ث — إذ كانت تعود قبل أن يُرسل العميل بايتاته حين لا يكون العامل
    قد جُدوِل بعد، فيُخدَم «طلبٌ لم يُقرأ».

والنتيجة النهائية المُعلَنة — بلا خضرة: Quality gate يبقى أحمر على خطوة خطّ الأساس
وحدها بـثلاثة اختبارات، كلٌّ باسمه وموضعه ونصّ فشله:

الاختبار الموضع نصّ الفشل الحرفي الدعوى
telegram::tests::t4_a_user_with_four_files_does_not_block_another_user telegram.rs:10936 «لم تبدأ المهامّ الخمس: [1000, 1002]» كل المهامّ بدأت
telegram::tests::t9_the_intro_is_pinned_once_at_the_moment_the_bot_is_added telegram.rs:11158 «النصّ سطران كما في التصميم: …» شكل نصّ التعريف
slots::tests::a_free_slot_is_taken_without_waiting_and_a_taken_one_reports_its_wait slots.rs:3697 «فتحة محجوزة ⇒ انتظار فعلي، ولا بد أن يُعلَن» إعلان زمن الانتظار

وثلاثتها دعاوى توقيت، وثلاثتها محلياً خضراء في كل قياس، والمجموعة كاملة محلياً
447 ناجحاً · ٠ فاشلاً · ٥ مُهمَلة
(128.75 ث). ⇒ حسّاسية توقيت على عدّاء مشترك، ولم تُهمَل
ولم تُقنَّع ولم يُعدَّل خطّ أساس
، ولا ادّعاء خضرة.

وفرضية التنظيف فُحِصت وسقطت — بالقياس لا بالرأي (2026-10-03): قِيل إن «يتامى يُبقون خادماً
مشغولاً». والقياس: (١) لا منفذ ثابت في telegram/slots — الربط كلّه
bind("127.0.0.1:0") (منفذ عابر يختاره النظام) ⇒ لا تصادم منافذ. (٢) ولا أبناء يتامى:
بعد تشغيل الثلاثة صفر عملية HaramLite أو haramlite_rs_lib باقية، وصفر مقبس متروك.
(٣) والجدول: ثلاث تشغيلات «مع يتامى» وثلاث «بعد قتلهم» ⇒ 6/6 نجحت في 17.7 ث،
و**orphans_before = 0 في الستّة جميعاً** — فلم يكن هناك يتامى يُقاس عليهم أصلاً، والفرضية
مردودة. (٤) وJobQueue::pop() يحجب على شرط، وshutdown() يوقظ كل المحجوبين
(notify_all) ⇒ لا عمّال عالقون عند الإنهاء.

وعلوقٌ واحد أُعلن بلا تفسير مُدَّعى: في قياس واحد علِق الثنائي (t4 وt9 معاً) واستهلك
1960 ثانية معالج حتى أوقفتُه، والسجلّ يقول has been running for over 60 seconds — وهي
إشارة علوق لا فشل. ولم يتكرّر في ٦+٦ تشغيلات بعده، ولم أُعدّل شيئاً بسببه: علّةٌ لا
تُعاد إنتاجها لا تُصلَح تخميناً، والفرضية **مطروحة للفحص


٣) المؤجَّل (بندٌ بند · بسببه)

البند رقم السطر سبب التأجيل
سقف طوابير تلغرام لكل مستخدم — الافتراضيّ ١ والسقف المسموح ٢ ARCHIVE/0.2.9PLAN.md:341 (القرار ١١) · docs/BACKLOG-0.3.md:281 (ز-٩/٩) و:299 (ز-١٠/٢) يحتاج إعادة هيكلة (مُوزِّع FIFO لكل user_id بدل مسار عالمي واحد)، والسقف يُقاس قبل تثبيته: أسوأ حالة معروفة 7943 MiB من 8192 (DirectML · ملف طويل) ⇒ الهامش 249 MiB، والثالث لم يُقَس. فالتثبيت على رقم غير مقيس ممنوع
توسيع pace_group_send للوسائط src-tauri/src/telegram.rs:797 (والتعليل في :788-796) الكلفة زمن انتظار لا يُقاس بلا مجموعة حقيقية (يحتاج بوتاً وchat حقيقيّاً = x3-tg-report.md §٨)، والتصميم الحالي يضع الانتظار على خيط الكاتب وحده فلا يحجب /kill — وتوسيعه للوسائط يغيّر ترتيب ما يصل السلك (وهو سلوك مُختبَر)
تكرار stopPoll (content.js:306-307) ARCHIVE/review-0.2.9-cline.md:259 و:233 (البند ١١) «نظافة تحرير» بلا أثر سلوكي مقيس؛ ويحتاج مُفسَداً («حذف النداء الصحيح») حتى لا يكون الحذف تخميناً — والملف محروس فتغييره يستلزم تحديث الحارس معه
توافق قديم/جديد في الإضافة (إضافة قديمة + تطبيق جديد · وتطبيق قديم + إضافة جديدة) ARCHIVE/0.2.9PLAN.md:178-181 و:197-198 (صفّا المصفوفة) الإضافة تراكمية في 0.2.9 (page_kept وmode حقول جديدة، وkept يحفظ معناه) ⇒ التقادم آمن الآن. وتغيير معنى حقل مستقبلاً يلزمه state_version في حالة الجسر — وصفّا المصفوفة يحتاجان جهازاً ويد المالك، فلم يُملآ
نقل الاستدلال إلى عملية عاملة (ليُقطع نداء ONNX الجاري) ARCHIVE/0.2.9PLAN.md:333 (القرار ٢) قطع نداء ONNX غير ممكن داخل العملية؛ والإلغاء اليوم تعاوني عند حدود المراحل مع قتل فوري لـffmpeg/yt-dlp. ويستلزم عملية عاملة + سِجلّ PIDs + إثبات «صفر عمليات يتيمة»

٤) ما لا تقوله هذه الوثيقة (تحديثات صريحة)

  1. نُشر الإصدار فعلاً — وُضع الوسم v0.2.9 على fca5882، وبنى Release
    (#36136806340) المثبِّتَ على عدّاءات GitHub ⇒ completed · success، ونُشر على GitHub
    خمسة أصول بأسمائها وأحجامها في جدول الترويسة أعلاه. والبصمات تحقّقت ثلاث مرات: نصّ
    SHA256SUMS.txt المنشور · وdigest في واجهة GitHub · وحساب SHA-256 محلياً بعد تنزيل
    الأصلَين
    (تحقّق 2026-10-03) ⇒ الثلاثة متطابقة.
  2. main تقدّم على التزام البناء fca5882 بعد النشر — والفرق موثَّق لا مُفترَض
    (مقيس 2026-10-03 بـgit diff --name-only fca5882..HEAD): ملفّات docs/ وحدها، ومثلها
    تعليقٌ في scripts/check-ui-live.mjs
    (‏8 أسطر مضافة، صفر أسطر محذوفة، وكلّها نصّ
    تعليق يوثّق أن وصفة منفذ التنقيح لا تعمل على 0.2.9). وصفر تغيير على src/
    وsrc-tauri/ وbrowser-extension/
    : git diff --numstat fca5882..HEAD -- src src-tauri browser-extension فارغ. ⇒ فالأصول المنشورة مبنية من كود لم يتغيّر بعده.
  3. بنود §١-٥ · §١-٦ · §١-٨ · §١-٩ · §١-١٠ (جزء b348c38) ليست في فرع التكامل بعد،
    وقد ذُكرت بأرقام فروعها صراحةً. ومعنى ذلك أن 0.2.9 كما هو اليوم لا يحمل حذف المسارات
    الميتة ولا إصلاح stderr ولا ترجمة رسائل الواجهة.
  4. SSRF ليس من عمل 0.2.9 (§١-١١) — أُثبت بفرق فارغ على yt_dlp.rs وبأن مُدخِله سلفٌ لـmain.
  5. الأرقام محلية على هذا الجهاز (‏cargo 1.95.0 · clippy 0.1.95) ولا تُنسب إلى CI.

٥) تحديث الموقع بعد النشر (‏c0bf651 · a8747a6)

العطل المقيس: الرئيسية تقرأ releases/latest عند فتحها، فكان الزائر بجافاسكربت وشبكة
يرى 0.2.9 أصلاً — والمكسور هو المكتوب في الشجرة: احتياطي 0.2.8 (٢٢ موضعاً في
docs/index.html) · بصمتا 0.2.8 · مقاسان (187.6 MB و364.7 MB) · وقسم «الجديد» الذي كان
محتوى 0.2.8 تحت عنوان يُبنى من {v} فيقرأ 0.2.9.

ما صار: ٣٣ موضعاً في ستّة ملفات — الرقم والروابط الثلاثة إلى v0.2.9 والبصمتان المقيسان
والحجمان المقيسان (187.8 MB من 196,944,377 بايت · 219.3 MB من 230,002,688 بايت)
ومداخل «الجديد» من §١ أعلاه · وثلاثة أزرار تحميل كانت مجمَّدة على v0.2.4/0.2.7 صارت
<span class="js-release-tag"> حيّة فلا تتقادم. والناتج: 0.2.8 = 0 · 0.2.9 = 33.

القياس: pnpm site:check ⇒ exit 0 · check-site-latest-trust.cjs و--selfcheck ⇒ exit 0
· وpages build and deployment على الالتزامين ⇒ completed success · والصفحة الحيّة
https://haramlite.com ⇒ 200 و33 × 0.2.9 · 0 × 0.2.8.