דלגו לתוכן

שיחות בקשות משיכה

בקשת משיכה היא ממילא המקום שבו מתרחשת השיחה על הקוד. שיחות בקשות משיכה הופכות אותה למשטח בסביבת עבודה של Control Center, באותה דרך שבה גשר צ’אט הופך את Slack למשטח כזה: תייגו ב-@ את הבוט של ה-GitHub App של השרת בבקשת משיכה, השיבו בתוך שרשורי הסקירה שלו, או הוסיפו את התווית ai-review, והעבודה מתרחשת במרחב הסקירה של ה-PR — תחת אותו ייחוס ואותם מעקות בטיחות כמו כל מה שמוקלד באפליקציה. הערה גם מותנית בחברות; התווית לא, מפני שאין לה מחבר. תשובות הבוט חוזרות ב-GitHub, במסלול שבו נשאלה השאלה.

שלוש החלטות מעצבות את התכונה, וכל אחת מהן היא אותה החלטה שגשר הצ’אט קיבל עבור צ’אט, מנוסחת מחדש עבור מארח קוד: לאיזה כיוון רץ החיבור, מה מעניק הרשאה להערה שמגיעה מבחוץ ולמי מיוחס כל צד.

הסריקה היא אמצעי התעבורה

קטע בשם “הסריקה היא אמצעי התעבורה”

ערוץ הדחיפה היחיד של GitHub עבור אפליקציה הוא webhook, ו-webhook זקוק לכתובת נכנסת — הדבר האחד שהמשטח הזה בכוונה לעולם אינו דורש. לכן הגילוי הוא סבב: בערך כל דקה, חיפוש אחד עם כינוי לכל התקנת אפליקציה מבקש מ-GitHub את בקשות המשיכה שמאזכרות את הבוט (או את ה-slug הקצר שלו) ואת בקשות המשיכה הפתוחות שנושאות את תווית הסקירה; בקשות משיכה שכבר יש להן מרחב סקירה נקראים שרשורי ההערות שלהן; והכול מושווה לפי מזהה הערה מול מה שנראה בסבב הקודם.

השלכות העיצוב ראויות לציון:

  • השהיה היא מרווח של סבב, לא אירוע. אישור ה-👀 נוחת בתוך כדקה. שום דבר דחוף אינו תלוי בכך.
  • פעם אחת בדיוק, לכל הערה. מזהי הערות ייחודיים ברחבי GitHub; כל אחד נרשם ברגע שהוא מסווג כמתאים, לפני שדבר כלשהו פועל עליו. קריסה יכולה לאבד את הטיפול בהערה, אבל לעולם לא להפעיל אותה פעמיים.
  • זמן לא מקוון מושלם, לא משוחזר. קבוצת מה שנראה נשמרת בדיסק, כך שהפעלה מחדש מספקת את מה שהגיע בזמן שהשרת היה למטה. היוצא מן הכלל היחיד הוא הסבב הראשון אי פעם: בלי זיכרון להשוות מולו, כל מה שתלוי ועומד נרשם בשקט — מפעיל שתייג עשרים בקשות משיכה לפני שהשרת הזה נוצר אסור שיחזור לעשרים סקירות שרצות.
  • התווית היא קבוצה, לא אירוע. החיפוש מדווח על נוכחות של תווית, כך שבקשת משיכה מתויגת מופעלת פעם אחת, ורק פעם אחת. הוספה מחדש של תווית לאחר הסרתה אינה נראית לסבב; אזכור הוא הנתיב להרצה חוזרת.

החיפוש רץ פעם אחת לכל חשבון התקנה, בלקוח של אותו בעלים עצמו, מפני שאסימון התקנה רואה רק את המאגרים של ההתקנה שלו — אישור גישה יחיד ברמת השרת היה מחמיץ בשקט כל בקשת משיכה תחת שאר ההתקנות של האפליקציה.

החברות מעניקה הרשאה, לא ההערה

קטע בשם “החברות מעניקה הרשאה, לא ההערה”

הערה בבקשת משיכה מגיעה כשהיא נושאת מעט יותר משם המשתמש של המחבר — זהות חיצונית שאינה מאומתת עד שהיא מוכחת. הבוט פותר אותה מול אינדקס הפוך של חברי סביבת העבודה ושבי חשבונות ה-GitHub המקושרים שלהם, שנבנה מחדש לפי TTL מפני שאנשים נכנסים ומוזמנים בזמן שהשרת רץ.

כשל סגור נראה כמו סולם, וכל שליב עונה בשרשור שבו סירב:

  • שם משתמש לא מוכר — כזה שאינו מקושר, או שמקושר למישהו שאינו חבר בסביבת העבודה הזו — נענה שעליו להיכנס ולבדוק שהוא אכן שייך
  • חבר עם הרשאת קריאה בלבד (viewer או guest) נענה שהתפקיד שלו אינו יכול להתחיל עבודה

שני מקרי שם המשתמש הלא מוכר חולקים תשובה אחת: האינדקס ההפוך מחזיר רק חבר של סביבת העבודה הזו, כך שחשבון לא מקושר וחשבון מקושר שאינו חבר הם אותה החמצה. אף אחד מהם אינו ממשיך. הערה לעולם אינה מיוחסת למי שלא שלח אותה, ומילותיו של זר לעולם אינן נכנסות לסביבת עבודה שהוא אינו יכול לראות.

לתווית ai-review אין מחבר לפתור, ולכן היא אינה מטפסת בסולם הזה. הוספה שלה מתחילה את הסקירה בסביבת העבודה שכבר מקשרת את המאגר.

כל צד של השיחה נושא זהות אחרת, וההבדל הוא התכונה:

  • ב-GitHub, הבוט הוא השרת. כל הערה, ריאקציה ותשובה שהגשר מפרסם נוסעת על אסימון ההתקנה של האפליקציה. איש לא לחץ על דבר בלקוח, ולכן שום דבר לא שואל את החשבון של אדם — אותו כלל שמרחיק webhooks וסריקה מאישורים של בני אדם.
  • במרחב, השאלה היא שלכם. ההודעה מיוחסת לחבר שאליו נפתר המחבר, והרצת הסוכן מתבצעת בשמו: הסוכן נרשם כשותף בקומיטים, וחוגת האוטונומיה ומעקות הבטיחות לפעולות של המרחב חלות בדיוק כפי שהן חלות על הודעה שמוקלדת באפליקציה. פעולות כתיבה למארח הקוד מההרצה עדיין יוצאות כהאפליקציה — אותו כלל כמו בנקודה שלמעלה — ולא על האסימון האישי של החבר.

פרסום ממצאי הסקירה חזרה ל-GitHub נשאר החלטה של אדם, שמתקבלת ב-Control Center. אזכור יכול להתחיל סקירה; רק אדם מפרסם אחת.

הגנת הלולאה, והשם המקוצר

קטע בשם “הגנת הלולאה, והשם המקוצר”

כל מה שנכתב על ידי חשבון בוט נזרק לפני שרץ שער כלשהו — שלנו במיוחד, וכל סיומת [bot], כך שגם בוט הסקירה של אפליקציה אחרת לא יכול להעיר את שלנו. בלי זה, התשובות של הבוט עצמו היו מגיעות כהערות חדשות ומפעילות אותו שוב, לנצח.

סיומת ה-[bot] היא גם הסיבה שהצורה המקוצרת קיימת. GitHub קובע את שם המשתמש של חשבון אפליקציה כ-<slug>[bot], אינו מציע השלמה אוטומטית עבורו, ולעולם אינו מפענח את @slug לאפליקציה — כך שקבלת ה-slug החשוף היא החלטה של מנגנון ההתאמה שלנו, וגילוי שלו דורש מסלול חיפוש גולמי בטקסט ההערה ולא באינדקס האזכורים של GitHub. המחיר מוצהר ולא מחובר: משתמש אנושי שחולק את ה-slug של האפליקציה פונה אל הבוט בכל אזכור קצר של עצמו. שמות אפליקציה ייחודיים הופכים את ההתנגשות להיפותטית.

  • בלי פרסום אוטומטי. הממצאים נוחתים במרחב הסקירה; פרסום ל-GitHub הוא פעולה נפרדת, של אדם
  • בלי זהות מטושטשת. המחבר נפתר לחבר אחד בדיוק או נדחה — אין ייחוס על בסיס ניחוש
  • בלי webhook של GitHub App. המשטח הזה לעולם אינו רושם אחד כזה, ולכן אין כתובת נכנסת לחשוף עבור הערות בבקשות משיכה. webhooks אחרים של המוצר (פייפליינים, סנכרון כרטיסים) הם הגדרה נפרדת ואופציונלית.