تخطَّ إلى المحتوى

خطوط الأنابيب والأتمتة

خطوط الأنابيب هي النصف الحتمي من Control Center. الوكيل يقرر ما يقوله، وخط الأنابيب يقرر ما يحدث بعده، بالطريقة نفسها في كل مرة. خط الأنابيب مخطط DAG من خطوات: يرسل وكلاء، ويشغّل سكربتات، ويتفرّع على شرط، وينتشر على مجموعة، ويمرّر البيانات بين الخطوات. وهو محفوظ، وقابل للاستئناف، وتُحسب تكلفته.

وُجدت لثلاثة أسباب: لربط عملية متكررة من عدة خطوات، ولتنسيق عدة وكلاء بالتتابع أو بالتوازي، وللاستجابة لما وقع من غير أن يكون هناك إنسان.

القالب (PipelineDefinition) هو المخطط التصريحي: الخطوات، والمدخلات المعلنة، والتهيئة. يعيش في قاعدة بيانات مساحة العمل نفسها. التشغيل (PipelineRun) تنفيذ واحد لهذا القالب، يحمل حقيبة حالة قابلة للتغيير، ومجموع التكلفة والرموز، وحالة كل خطوة.

للقالب الواحد تشغيلات كثيرة. تنجو التشغيلات من إعادة الإقلاع: يعيد المحرّك تحميل التشغيلات الجارية ويلتقطها من حيث توقفت.

هناك ضمانتان تُفشلان التشغيل كله عمدًا بدل استئناف الخطوة، لأن الاستئناف هنا أسوأ من التوقف:

  • خطوة بقيت suspended أطول من suspendedStepTimeout (24 ساعة افتراضيًا) تنتظر تذاكر لن تبلغ حالة نهائية، فتُفشَل حتى يتحرر التشغيل. الاستثناء هو extras['awaitApproval'] == true، وهو بوابة الموافقة الجزئية orchestration.awaitApproval. human.gate ليس مستثنًى.
  • خطوة أُعلنت extras: {'idempotent': false} وكانت في منتصف التنفيذ حين توقف الخادم ربما تكون قد طبّقت أثرها الجانبي أصلًا (دمج squash، أو تنظيف شجرة عمل). إعادتها قد تطبّق الأثر مرة ثانية، فيفشلها المحرّك ويترك لك أن تعيدها.

للخطوة جزءان متعامدان، وإبقاؤهما منفصلين هو ما يُبقي المحرّك صغيرًا.

النوع (StepKind) يخبر المحرّك متى تصبح الخطوة جاهزة للتشغيل بالنسبة إلى الخطوات التي تسبقها. هذا كل ما يحتاج المجدول إلى معرفته. وهي ستة: trigger وlisten وjoin وrouter وforEach وterminal. الجسم (bodyKey) هو العمل الذي تؤديه الخطوة متى جُدولت. الأجسام دوال مغلقة تُسجَّل بمفتاحها عند إقلاع الخادم، فتستطيع خطوات كثيرة من أنواع مختلفة أن تشترك في جسم واحد. عدة خطوات listen مثلًا تشغّل كلها conversation.promptAgent.

ولأن معنى الجدولة محصور في ستة أنواع، يكبر فهرس السلوك من غير أن يمس أحد المجدول. انظر أنواع خطوات خط الأنابيب للفهرس الكامل للأنواع والأجسام وتهيئتها.

نتيجتان لتصميم السجل يجدر أن تعرفهما قبل أن تكتب قالبًا بيدك:

  • خطوة ليس bodyKey الخاص بها مسجّلًا تُصدر تحذيرًا عند تحميل القالب ثم لا تُنفَّذ. لا شيء يفشل بصوت عالٍ أثناء التنفيذ. خطوتا trigger وterminal مستثنيتان من هذا التحذير: المشغّل يظل يشغّل جسم pipeline.trigger الذي لا يفعل شيئًا حتى تنطلق المستمعات بعده، وخطوة terminal بلا جسم، وهي حارس للمجدول.
  • مفاتيح خريطة extras على العقدة تُطابَق حرفيًا، وأي مفتاح بغير ذلك يُتجاهل بصمت. extras['spaceId'] وحده يضع عمل خطوة الوكيل في محادثة قائمة. يُقرأ extras['mode'] (chat / plan / review) على العقدة التي تفتح المساحة (messaging.createSpace، والافتراضي review). خطوة وكيل لاحقة لا تستطيع إعادة منح الإذن لمحادثة انضمّت إليها. أي كتابة أخرى لأي من المفتاحين تُتجاهل.

الخطوات لا تستدعي بعضها بعضًا. تقرأ من حقيبة حالة مشتركة على التشغيل وتكتب فيها: تقرأ الخطوة مدخلاتها بالمفتاح، ثم تنفّذ، ثم تكتب مخرجها تحت outputKey الخاص بها. يدمج StateReducer الكتابات المتزامنة إذا استهدفت الفروع المتوازية المفتاح نفسه، ويستبدل TemplateRenderer مواضع {{...}} في المطالبات والسكربتات من الحالة نفسها.

لهذا يمكن فحص خط الأنابيب بعد انتهائه: حقيبة الحالة هي قصة التشغيل كلها.

صفوف PipelineTrigger تعلن متى يبدأ القالب من تلقاء نفسه. الأنواع أربعة:

المشغّل يبدأ التشغيل عندما
حدث نطاق يُطلَق حدث نطاق مطابق، مثل PullRequestPublished أو TicketAssigned
جدولة يحين موعد تعبير cron أو فاصل زمني
يدوي تشغّله بيدك، ووجود مشغّل يدوي مفعّل هو ما يضع القالب في منتقي تشغيل خط أنابيب
Webhook يرسل نظام خارجي POST إلى /webhooks/<token> على منفذ HTTP للخادم

مشغّلات الأحداث مجموعة مغلقة: 16 نوعًا يعرفها الموزّع وتُعرض في المنتقي (EventPayloadMapper.knownEventTypes)، ولا يستطيع شيء سواها بدء خط أنابيب. لأربعة عشر من هذه الستة عشر تعيين للحمولة. أما TicketCreated وTicketStatusChanged فلا تعيين لهما، فيظل المشغّل على أي منهما مفعّلًا ولا ينطلق. انظر أحداث النطاق للقائمة الكاملة.

رمز Webhook هو سر HMAC في الوقت نفسه: تُتحقق ترويسة الطلب X-Hub-Signature-256: sha256=<hex> مقابل HMAC-SHA256(body, token)، وتُرفض التسليمات غير المتحققة ولا تُعاد، ويُسقط dedupe_key المكررات. ولأن صفوف المشغّل فريدة على (مساحة العمل، نوع الحدث، القالب)، لا يحمل القالب أكثر من مشغّل Webhook واحد، ولا أكثر من مشغّل واحد لكل نوع من أحداث النطاق.

تُبذر القوالب المضمّنة المشغّلات اليدوية ومشغّلات الأحداث مفعّلة، وتُبذر مشغّلات الجدولة معطّلة، فالمسح المتكرر لا يعمل إلا إذا فعّلته.

يُبذر في كل مساحة عمل ثلاثة عشر قالبًا لخطوط الأنابيب، وثمانية منها تأتي معطّلة: external_pr_welcome وcross_review وticket_to_pr وpr_triage وpre_merge_gate وrelease_notes وdep_audit وpr_digest.

هذا ما يفاجئ الناس. تُبذر مشغّلاتها مفعّلة، لكن PipelineEngine.start يرفض القالب المعطّل ويعيد null، فينطلق المشغّل في فراغ ولا يظهر تشغيل. القالب هو المفتاح، لا المشغّل. فعّله من الإعدادات ← مساحة العمل ← قوالب خطوط الأنابيب قبل أن تنتظر من أي من هذه الثمانية أن يعمل.

إعادة بذر مساحة العمل تُبقي ما اخترته: يبقى إعداد isEnabled، وتبقى صفوف المشغّلات القائمة. الاستثناء المقصود واحد: الجدولة اليومية لـ pr_merged_cleanup تُفعَّل قسرًا ولو كانت مطفأة من قبل، لأنها تجمع المخلفات. من غيرها تتراكم صفوف أشجار العمل، وأقسام مخطط الشفرة، ونُسخ آلية النسخ عند الكتابة، ولم يُقصد أن يُترك إيقافها خيارًا للمستخدم.

وهناك اختلاف آخر. القالب المضمّن pr_review كفّ عمدًا عن النشر على GitHub. نشر المراجعة خطوة منفصلة ومقصودة يتخذها إنسان، وخط أنابيب ينشر بنفسه كان سينشر مرتين. cross_review وticket_to_pr وpr_triage ما زالت تنشر تعليقًا على GitHub مباشرة حين تعمل.

ينشئ PipelineEngine سجل التشغيل، ويجدول عقدة البداية التي تطابق نوع حدث التشغيل، ثم يمشي مخطط DAG: كلما اكتملت خطوة يقيّم المشغّلات التي بعدها، ويحسم فروع router، وينتظر عند join، ويطبّق سياسات إعادة المحاولة والمتابعة عند الفشل، ويحفظ الحالة والتكلفة والأخطاء عند كل خطوة. الحواف التي تخرج من بداية أخرى تُتجاهل في هذا التشغيل، فانطلاق الجدولة لا ينتظر شرطًا خاصًا بالبدء اليدوي.

ما لا يعرفه هو أي شيء عن التذاكر، أو الإرسال، أو GitHub، أو المراسلة. الوصل الوحيد لتوسيعه هو PipelineBodyRegistry. الأجسام المسجّلة فيه هي التي تحمل AgentDispatchPort وMessagingPort ونظائرهما، ولهذا فإن إضافة قدرة إلى خطوط الأنابيب لا تقتضي تغيير المجدول.

الخطوة التي تفشل تسجّل رسالة الخطأ وتنتقل إلى failed. إن أعلنت الخطوة continueOnFail، تمضي الخطوات التالية ويُودَع الخطأ في حالة التشغيل، وإلا فشل التشغيل. إن كان للخطوة StepRetryPolicy أُعيدت أولًا، بإمهال خطي أو أسي، قبل أن يُطبَّق أي من الأمرين.