رفتن به محتوا

پایپ‌لاین‌ها و خودکارسازی

پایپ‌لاین‌ها نیمهٔ قطعیِ Control Center هستند. یک عامل تصمیم می‌گیرد چه بگوید؛ یک پایپ‌لاین تصمیم می‌گیرد قدم بعد چه رخ دهد، هر بار به همان شکل. یک پایپ‌لاین یک DAG از گام‌هاست که می‌تواند عامل‌ها را اعزام کند، اسکریپت اجرا کند، روی یک شرط شاخه بزند، روی یک مجموعه پخش شود و داده را میان گام‌ها بگذراند — ماندگار، قابل‌ازسرگیری و با هزینهٔ محاسبه‌شده.

پایپ‌لاین‌ها به سه دلیل وجود دارند: به‌هم‌زنجیر کردن یک فرایند چندگامی تکراری، هماهنگ کردن چند عامل به‌صورت متوالی یا موازی، و واکنش به چیزی که بدون حضور انسان رخ داده است.

یک قالب (PipelineDefinition) همان DAG اعلانی است: گام‌ها، ورودی‌های اعلان‌شده و پیکربندی. قالب در پایگاه‌دادهٔ خودِ فضای کاری زندگی می‌کند. یک اجرا (PipelineRun) یک بار اجرای همان قالب است که یک محفظهٔ حالت تغییرپذیر، مجموع هزینه و توکن و وضعیت هر گام را با خود دارد.

یک قالب اجراهای زیادی دارد. اجراها از راه‌اندازی دوباره جان سالم به در می‌برند: موتور، اجراهای در جریان را دوباره بارگذاری می‌کند و آن‌ها را از همان‌جا که متوقف شده بودند ادامه می‌دهد.

دو پشتوانهٔ ایمنی عمداً کل اجرا را ناموفق می‌کنند به‌جای اینکه یک گام را از سر بگیرند، چون ازسرگیری بدتر از توقف است:

  • گامی که بیش از suspendedStepTimeout (به‌طور پیش‌فرض 24 ساعت) در وضعیت suspended مانده است، منتظر تیکت‌هایی است که هرگز به وضعیت پایانی نمی‌رسند؛ پس ناموفق می‌شود تا اجرا آزاد شود. استثنا extras['awaitApproval'] == true است — همان دروازهٔ تأیید جزئی orchestration.awaitApproval. human.gate مستثنا نیست.
  • گامی که با extras: {'idempotent': false} اعلان شده و هنگام توقف سرور در میانهٔ کار بوده، ممکن است اثر جانبی‌اش را از پیش اعمال کرده باشد (یک squash merge، یک پاک‌سازی worktree). اجرای دوبارهٔ آن می‌تواند اثر را دو بار اعمال کند، پس موتور آن را ناموفق می‌کند و تصمیم به تلاش دوباره را به شما می‌سپارد.

نوع زمان‌بندی می‌کند و بدنه کار را انجام می‌دهد

بخش با عنوان “نوع زمان‌بندی می‌کند و بدنه کار را انجام می‌دهد”

یک گام دو بخش متعامد دارد و جدا نگه داشتن آن‌ها همان چیزی است که موتور را کوچک نگه می‌دارد.

نوع (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 یا یک بازهٔ زمانی به موعد خود می‌رسد
دستی خودتان آن را دستی اجرا می‌کنید؛ وجود یک تریگر دستیِ فعال همان چیزی است که قالب را در انتخاب‌گر اجرا می‌گذارد
وب‌وکه یک سیستم خارجی روی پورت HTTP سرور به /webhooks/<token> درخواست POST می‌فرستد

تریگرهای رویدادی یک مجموعهٔ بسته‌اند: 16 نوع رویداد برای دیسپچر شناخته‌شده‌اند و در انتخاب‌گر عرضه می‌شوند (EventPayloadMapper.knownEventTypes) و هیچ چیز دیگری نمی‌تواند یک پایپ‌لاین را شروع کند. برای چهارده مورد از این 16 مورد نقشهٔ بار داده وجود دارد. دو مورد — TicketCreated و TicketStatusChanged — هیچ‌کدام ندارند، پس یک تریگر روی هر یک از آن‌ها فعال می‌ماند و هرگز به اجرا درنمی‌آید. برای فهرست کامل رویدادهای دامنه را ببینید.

توکن وب‌وکه هم‌زمان همان کلید محرمانهٔ HMAC هم است: هدر X-Hub-Signature-256: sha256=<hex> درخواست در برابر HMAC-SHA256(body, token) بررسی می‌شود، تحویل‌های تأییدنشده رد می‌شوند و قابل بازپخش نیستند و تکراری‌ها با dedupe_key حذف می‌شوند. چون ردیف‌های تریگر روی (فضای کاری، نوع رویداد، قالب) یگانه‌اند، یک قالب می‌تواند حداکثر یک تریگر وب‌وکه و یک تریگر به‌ازای هر نوع رویداد دامنه داشته باشد.

قالب‌های داخلی تریگرهای دستی و رویدادی خود را فعال می‌کارند؛ تریگرهای زمان‌بندی‌شده غیرفعال کاشته می‌شوند، پس یک جاروب دوره‌ای همیشه اختیاری است.

قالب‌هایی که غیرفعال عرضه می‌شوند

بخش با عنوان “قالب‌هایی که غیرفعال عرضه می‌شوند”

سیزده قالب پایپ‌لاین در هر فضای کاری کاشته می‌شوند و هشت مورد از آن‌ها غیرفعال عرضه می‌شوند: 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 حتی اگر پیش‌تر خاموش بوده باشد به‌اجبار فعال می‌شود، چون این کار جمع‌آوری زباله است. بدون آن، ردیف‌های worktree، پارتیشن‌های گراف کد و نسخه‌های کپی-هنگام-نوشتن روی هم انبار می‌شوند و این هرگز قرار نبود انتخاب کاربر باشد.

یک عدم تقارن دیگر هم ارزش دانستن دارد: قالب داخلی pr_review عمداً دیگر بازبینی را در GitHub منتشر نمی‌کند. انتشار یک بازبینی گامی جداگانه است که کاربر دروازهٔ آن را باز می‌کند و پایپ‌لاینی که خودش مستقیم پست بگذارد، دو بار منتشر می‌کند. cross_review، ticket_to_pr و pr_triage هنگام اجرا هنوز مستقیماً یک نظر در GitHub می‌گذارند.

PipelineEngine رکورد اجرا را می‌سازد، گرهٔ شروعی را که با نوع رویداد اجرا جور است زمان‌بندی می‌کند و سپس DAG را می‌پیماید: با کامل شدن گام‌ها، تریگرهای پایین‌دستی را ارزیابی می‌کند، شاخه‌های router را حل می‌کند، روی joinها منتظر می‌ماند، سیاست‌های تلاش دوباره و continue-on-fail را اعمال می‌کند و حالت، هزینه و خطاها را در هر گام ماندگار می‌کند. یال‌هایی که از یک شروع دیگر بیرون می‌آیند برای آن اجرا نادیده گرفته می‌شوند، پس فعال شدن یک زمان‌بند منتظر شرطی که تنها با اجرای دستی برقرار می‌شود نمی‌ماند.

چیزی که نمی‌داند هر آن چیزی است دربارهٔ تیکتینگ، اعزام، GitHub یا پیام‌رسانی. تنها درزِ توسعهٔ آن PipelineBodyRegistry است. بدنه‌هایی که آن‌جا ثبت می‌شوند همان‌هایی هستند که AgentDispatchPort، MessagingPort و هم‌خانواده‌هایشان را نگه می‌دارند — به همین دلیل افزودن یک قابلیت به پایپ‌لاین‌ها هرگز به معنای تغییر زمان‌بند نیست.

گامی که شکست می‌خورد پیام خطایش را ثبت می‌کند و به failed می‌رود. اگر گام continueOnFail را اعلان کرده باشد، گام‌های پایین‌دستی ادامه می‌دهند و خطا در حالت اجرا انبار می‌شود؛ در غیر این صورت اجرا ناموفق می‌شود. گامی که StepRetryPolicy دارد، پیش از اعمال هر یک از این دو، با عقب‌نشینی خطی یا نمایی دوباره تلاش می‌شود.