رفتن به محتوا

چندنفره — هویت، عضویت و حضور

Control Center برای یک اپراتور تنها کار می‌کند، اما مدلی که زیر آن نشسته از همان آغاز چندکنشگری است. انسان‌ها و عامل‌ها در همان فضاها پیام می‌فرستند، همان تیکت‌ها را ویرایش می‌کنند و در همان فهرست ظاهر می‌شوند. ایمن و قابل‌فهم‌کردن این وضعیت به سه تصمیم فروکاسته می‌شود: چه کسی کنشگر شمرده می‌شود، چه چیزی دروازهٔ دسترسی است و کدام حالت به‌صورت ماندگار مشترک می‌شود و کدام فقط به‌صورت زنده پخش می‌شود.

Principal: کاربران و عامل‌ها کنشگرهایی هم‌ارزند

بخش با عنوان “Principal: کاربران و عامل‌ها کنشگرهایی هم‌ارزند”

هر چیزی که «چه کسی این کار را کرد» را ثبت می‌کند — پیام‌ها، تیکت‌ها، بازبینی‌ها، برنامه‌ها، گزارش اجراها، ورودی‌های حسابرسی — از راه یک Principal حل‌وفصل می‌شود؛ اجتماع مهروموم‌شده‌ای از UserPrincipal و AgentPrincipal با یک قالب سیمی فشرده (user:<id> / agent:<id>). اسناد هرگز N انسان (یا N عامل) را در یک حساب نگهبان واحد فرو نمی‌ریزد، پس بازبینی‌ای که یک عامل گذاشته و بازبینی‌ای که یک انسان گذاشته، هرجا که ظاهر شوند از هم قابل تشخیص‌اند.

چون Principal واحد هویت است، واحد مهار هم هست:

  • محدودیت نرخ به‌ازای هر Principal است، نه هر نشست. نگهبان سیلِ فراخوانی‌های ابزار از راه دور بر پایهٔ کاربر بودجه‌بندی می‌کند، نه بر پایهٔ اتصال: همهٔ دستگاه‌های یک عضو از یک پنجرهٔ لغزان مشترک برداشت می‌کنند (۱۲۰ فراخوانی در دقیقه که حداکثر ۳۰ تای آن‌ها می‌تواند تغییردهنده باشد)، پس باز کردن سه دستگاه سهم هیچ‌کس را سه‌برابر نمی‌کند و یک عضو پرسروصدا نمی‌تواند بقیه را گرسنه بگذارد.
  • واگرد به‌ازای هر Principal است. ژورنال کنش هر کلاینت فقط تغییراتی را ثبت می‌کند که Principal خودش انجام داده است. می‌توانید ویرایش تیکت خودتان را با ⌘Z واگرد کنید؛ نمی‌توانید ویرایش یک هم‌تیمی را واگرد کنید — تا وقتی آن را می‌بینید، خط ماندگار پیشاپیش حلش کرده است (پایین‌تر را ببینید).

عضویت مرز دسترسی است — نه کلید جفت‌سازی

بخش با عنوان “عضویت مرز دسترسی است — نه کلید جفت‌سازی”

دو اعتبارنامهٔ جدا به دو پرسش جدا پاسخ می‌دهند. کلید جفت‌سازی (به کنترل از راه دور و موبایل نگاه کنید) یک دستگاه را در برابر سرور احراز هویت می‌کند. عضویت به کاربر آن دستگاه اجازه می‌دهد درون یک فضای کاری عمل کند. دستگاهی که جفت شده ولی کاربرش در یک فضای کاری عضویت ندارد، نه می‌تواند آن را بخواند و نه می‌تواند در آن تغییری بدهد.

عضویت یک سطر برای هر (فضای کاری، کاربر) است که در فایل پایگاه‌دادهٔ خودِ همان فضای کاری ذخیره می‌شود — پس «چه کسی در این فضای کاری است؟» یک خواندن معمولی و محدوده‌مند است، در حالی که پرسش وارونه، «من در چه فضاهای کاری‌ای هستم؟»، ناگزیر از چند فایل فراتر می‌رود و یکی از معدود پخش‌های مجاز میان فضاهای کاری است. همین چیزی است که انتخابگر فضای کاری پیش از انتخاب هر فضای کاری اجرا می‌کند.

هر سطر نقشی از یک نردبان پنج‌سطحی و مرتب‌برحسب‌رتبه حمل می‌کند:

نقش اختیارات
owner کنترل کامل، از جمله حذف فضای کاری؛ دقیقاً یکی در هر فضای کاری
admin مدیریت اعضا، دعوت‌نامه‌ها، تنظیمات و عملیات مخرب
member خواندن و نوشتن روزمره: پیام‌ها، تیکت‌ها، اجراهای عامل
viewer فقط‌خواندنی در سراسر فضای کاری؛ گلوب‌های استثنای راز، مسیرهای منطبق را در سطوح حامل کد پنهان می‌کنند
guest همان کفِ فقط‌خواندنیِ بیننده، در پایین‌ترین رتبهٔ نردبان

هر تصمیم دسترسی به «آیا نقش فراخوان دست‌کم X است؟» فروکاسته می‌شود. این کف از نوع عملیات استخراج می‌شود، مگر آنکه عملیات کفی را صریحاً اعلام کند: خواندن به guest نیاز دارد، یک تغییر به member و یک عملیات مخرب به admin. ACLهای به‌ازای هر شیء، فراتر از اعطاهای به‌ازای هر مخزن که پایین‌تر می‌آید، به‌عمد خارج از محدوده‌اند.

دعوت‌نامه‌ها راه اعطای عضویت‌اند، از تنظیمات → فضای کاری → اعضا. یک ادمین کدی یک‌بارمصرف (به‌صورت پیوند یا QR) صادر می‌کند که به‌طور پیش‌فرض پس از هفت روز منقضی می‌شود؛ سرور تنها هش SHA-256 کد را ذخیره می‌کند و خود کد دقیقاً یک‌بار نشان داده می‌شود. بازخرید آن، کاربر را در لحظه (JIT) تأمین می‌کند، عضویت را با نقش همان دعوت ثبت می‌کند (هرگز owner — مالکیت صریحاً منتقل می‌شود و هرگز با دعوت اعطا نمی‌شود) و سپس کار را به جفت‌سازی دستگاه می‌سپارد. اعتبارنامه‌های دستگاهی که پس از جفت‌سازی صادر می‌شوند خودشان به ۳۰ روز محدود شده‌اند، پس یک جفت‌سازی کلید دائمی نیست.

اعطاهای مخزن زیر عضویت می‌نشینند. چون سرور چک‌اوت‌های کامل را نگه می‌دارد، عضویت فضای کاری به‌تنهایی هرگز نباید از میزبان کد پیشی بگیرد: هر سطح حامل کد — فایل‌ها، diffها، بازبینی PR، جست‌وجوی کد — اعطای عضو روی همان مخزن را بررسی می‌کند. اعطاها به‌ازای (فضای کاری، کاربر، مخزن) تعریف می‌شوند و رتبه‌بندی‌شان none → read → review → write است؛ write یعنی هدایت اجراهای عاملی که در مخزن می‌نویسند. نبودن سطر یعنی none؛ مالک‌ها و ادمین‌ها به‌طور ضمنی روی هر مخزن پیوندشده write دارند و اصلاً سطری حمل نمی‌کنند.

Control Center حالت مشترک را به دو خط تقسیم می‌کند که قواعد ماندگاری‌شان وارونهٔ یکدیگر است، چون این دو نوع حالت به شکل‌های وارونه شکست می‌خورند.

حضور — چه کسی آنلاین است، کجاست، چه چیزی تایپ می‌کند، یک عامل همین حالا چه می‌کند — لحظه‌ای که کهنه شود بی‌ارزش است. اگر ماندگار می‌شد، هر اتصال دوباره باید «پایگاه‌داده می‌گوید سام دارد این تیکت را ویرایش می‌کند» را با واقعیت آشتی می‌داد. پس حضور هرگز در پایگاه‌داده نوشته نمی‌شود. آن در یک هاب حضور درون‌حافظه‌ای روی سرور زندگی می‌کند: کلاینت‌ها با presence.update منتشر می‌کنند، همتاها با presence.watch مشترک می‌شوند و ورودی‌ها پس از سه ضربان ازدست‌رفته به‌طور خودکار منقضی می‌شوند (ضربان‌ها هر ۱۰ ثانیه اجرا می‌شوند؛ هاب هر ۵ ثانیه پاک‌سازی می‌کند). چیزی برای آشتی دادن نیست — فقط باید بگذاریم از اعتبار بیفتد.

دو ویژگی این خط را قابل‌اعتماد نگه می‌دارد:

  • هویت از نشست می‌آید، نه از آرگومان‌های کلاینت. سرور دستگاه احراز‌هویت‌شدهٔ هر اتصال را به یک شناسهٔ کاربر نگاشت می‌کند؛ کلاینت هرگز نمی‌تواند چیزی جز حضور خودش را به‌روز کند، پس هیچ‌کس نمی‌تواند وضعیت یا موقعیت مشارکت‌کنندهٔ دیگری را جعل کند. یک به‌روزرسانی صریح offline (قطع اتصال یا کلید حالت نامرئی) ورودی را بی‌درنگ حذف می‌کند.
  • فهرست از هاب سرور می‌گذرد. همتاها هرگز حضور را مستقیم رد‌وبدل نمی‌کنند، پس هر مشارکت‌کننده فارغ از توپولوژی همان فهرست را می‌بیند. عامل‌ها کلاینتی ندارند، پس سرور ورودی‌هایشان را از حالت زندهٔ اجرا مصنوعی می‌سازد — در حال فکر، در حال اجرا، مسدود روی دروازهٔ تأیید، انجام‌شده — و یک اجرای نماینده تعیین می‌کند که حالت و موقعیت یک عامل چیست، تا اجراهای همزمان فهرست را به نوسان نیندازند.

پخش به تفکیک مصرف‌کننده پله‌ای است: رومیزی و وب روی یک پلهٔ کامل سوار می‌شوند (حداکثر یک انتشار فهرست در هر ۱۰۰ میلی‌ثانیه) و تلفن روی یک پلهٔ خلاصه (هر ۵۰۰ میلی‌ثانیه)، تا به‌روزرسانی‌هایی با آهنگ حرکت نشانگر PWA را ذوب نکنند.

موقعیت یک مشارکت‌کننده — اینکه همین حالا کجاست، تا سطح یک آرتیفکت مشخص — یک اجتماع مهروموم‌شده است: Space، File (مخزن + مسیر + شمارهٔ خط اختیاری)، Pr، Ticket، PlanNode. هر موقعیتی جایی برای پریدن به آن ندارد: مسیری برای نمایشگر فایل وجود ندارد، پس موقعیت File به‌جای مقصد، به‌صورت یک برچسب رندر می‌شود. این خط همچنین ادعاهای نرم را حمل می‌کند («سام دارد این تیکت را ویرایش می‌کند»، «یک عامل این worktree را گرفته است»): دیدپذیری تعارض، نه قفل. انواع ناشناختهٔ موقعیت به null تفسیر می‌شوند، پس یک کلاینت قدیمی به‌سادگی چیزی را که نمی‌فهمد رندر نمی‌کند.

رکوردهایی که باید زنده بمانند — تیکت‌ها، سند یادداشت‌های مشترک فضا، پیام‌ها — راه وارونه را می‌روند: مرجع، مرتب و ماندگارشده. ویرایش‌های همزمان با برندهٔ آخرین نویسنده در ترتیب دریافت سرور حل می‌شوند، هرگز با ساعت کلاینت. این حل‌وفصل به‌عمد ریزدانه است: ویرایش‌های تیکت به‌ازای هر ستون هستند، پس دو نفر که فیلدهای متفاوت یک تیکت را ویرایش می‌کنند هر دو می‌نشینند و هیچ‌کدام نوشتهٔ دیگری را از بین نمی‌برد (گذارهای گردش‌کار به‌جای آن قفل خوش‌بینانه را نگه می‌دارند)؛ سند یادداشت‌های فضا یک ستون است، پس کل سند LWW است و ادعاهای نرم روی خط حضور، ویرایش همزمان را به‌جای قفل‌شده، دیدنی می‌کنند. این به‌عمد یک CRDT نیست — بار اثبات بر دوش CRDT است و LWW با ترتیب دریافت، به‌همراه ادعاهای دیدنی، الگوهای واقعی همکاری را پوشش می‌دهد.

«آخرین نویسنده» یعنی ترتیب دریافت در سرور، هرگز ساعت کلاینت — لپ‌تاپی با ساعت کج نمی‌تواند ویرایشی را که باخته ببرد. پشت خط ماندگار، خوراک همگام‌سازی نشسته است: تریگرهای SQLite هر تغییر را درون همان تراکنش نویسنده به یک گزارش تغییر به‌ازای هر فضای کاری می‌افزایند و در همان تراکنشِ تغییر، یک شمارهٔ ترتیب یکنوا به‌ازای فضای کاری تخصیص می‌دهند. یک سرویس سرور آن گزارش را دنبال می‌کند و به ترتیب شماره، به قاب‌های دلتای نسخه‌دار بدلش می‌کند. کلاینت‌ها از عکس‌های کامل به دلتاها فرگشت می‌یابند؛ یک شکاف در شماره‌ها، آن مخزن را به‌جای اعمال نادرست قاب‌ها، به حالت عکس کامل برمی‌گرداند.

دنبال‌کردن، هدایت، تصاحب، بازگرداندن

بخش با عنوان “دنبال‌کردن، هدایت، تصاحب، بازگرداندن”

خط حضور فقط یک فهرست نیست — فعل‌های همکاری را به حرکت درمی‌آورد:

  • حالت دنبال‌کردن. روی یک مشارکت‌کننده کلیک کنید و کلاینت شما سوار موقعیت او می‌شود و همان‌جا که او می‌رود پیش می‌رود. دنبال‌کردن «تا وقتی خودت کاری کنی» است: همان لحظه که خودتان پیمایش کنید، دنبال‌کردن جدا می‌شود. بنری قابل‌بستن اعلام می‌کند وقتی مشارکت‌کنندهٔ دیگری فضایی را که شما در آن هستید در حال ارائه (نورافکنی) است.
  • هدایت. پیامی که به یک عامل در حال اجرا فرستاده می‌شود، به‌عنوان هدایت در صف می‌نشیند؛ عامل پیش از ازسرگیری نوبت بعدی‌اش آن را دوباره می‌خواند، پس می‌توانید در میانهٔ راه به یک اجرا تلنگر بزنید، بی‌آنکه بکشیدش.
  • تصاحب. در دست گرفتن کنترل یک گفتگو، هر اجرای فعال را در نخستین مرز نوبت پاکش متوقف می‌کند (یک اجرای Claude Code که مرز ایمن ندارد، متوقف می‌شود) و پشت یک عکس لحظه‌ای git به‌ازای هر نوبت قرار می‌دهد، پس تصاحب برگشت‌پذیر است. یک نشانگر ماندگار، اعزام‌های تازه به آن فضا را مسدود می‌کند و از راه‌اندازی دوبارهٔ سرور جان سالم به در می‌برد — سرور هرگز به‌طور خودکار به میانهٔ ویرایش نیمه‌تمام یک انسان بازنمی‌گردد. یک ویرایشگر زنده به‌ازای هر worktree یک ادعای نرم است: تصاحب دوم دیده و رد می‌شود، نه اینکه بی‌صدا ادغام شود.
  • بازگرداندن. پس دادن کنترل، خلاصه‌ای ساختاریافته از diff همان worktree را در فضا منتشر می‌کند، آن را به‌عنوان هدایت برای اجراهای متوقف‌شده در صف می‌گذارد، آن‌ها را از سر می‌گیرد و نشانگر را پاک می‌کند.

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

سطح مقدار سیمی اثر روی یک فراخوانی ابزار دروازه‌دار
فقط پیشنهاد proposeOnly یکسره رد می‌شود، همراه با دلیلی که به عامل گفته می‌شود بازگو کند — می‌تواند کنش را توصیف کند اما انجامش ندهد
عمل با تأیید actWithApproval دروازهٔ تأیید بسته‌در-نبود-پاسخ: یک تأیید به اپراتور متصل نشان داده می‌شود و نبود تأییدکننده یعنی رد
عمل آزاد actFreely پیش‌تأییدشده، مگر جایی که یک قاعدهٔ deny سختِ گاردریل اعمال شود — قاعدهٔ deny همیشه برنده است
(تنظیم‌نشده) null همانند عمل با تأیید: پیش‌فرض سرور

دو چیز در هر دو جهت بیرون از این درجه می‌مانند. فعل خروجیِ خودِ یک حالت سنجاق شده است و هرگز نمی‌تواند رد شود، هر چه درجه بگوید — زمانی حالت orchestrate به‌طور ساختاری نمی‌توانست تنها تحویل‌دادنی‌اش را منتشر کند، چون یک پیش‌تنظیم کلاس اثری را که آن فعل اعلام می‌کند رد می‌کرد. و یک deny سخت از گاردریل‌های کنش با actFreely قابل بازنویسی نیست.

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