رفتن به محتوا

فضاهای کاری و ایزوله‌سازی

فضای کاری، مستأجر ایزوله‌سازی سطح‌بالا در Control Center است. عامل‌ها، مخزن‌ها، فضاها، تیکت‌ها، حافظه، پایپ‌لاین‌ها و اعضا را در یک بافت کران‌دار گرد هم می‌آورد. هر چیزی که درون یک فضای کاری رخ بدهد، درون همان می‌ماند.

یک فضای کاری نمونه به یک پروژه یا یک کدبیس نگاشت می‌شود. ممکن است برای «api-service»، «frontend-v2» و «infrastructure» فضاهای کاری جداگانه داشته باشید.

ردیف‌های یک فضای کاری در فایل SQLite خودِ آن فضای کاری، در مسیر <dataDir>/<workspaceId>/workspace.db، زندگی می‌کنند. هر فضای کاری یک پوشه می‌گیرد نه یک فایل تنها، چون یک فضای کاری بیش از یک پایگاه داده انباشته می‌کند — worktreeهای هر گفتگو، فایل‌های عامل و مهارت، اعتبارنامه‌های ربات گفتگو — و دادن یک پوشه به آن یعنی همهٔ این‌ها با هم زندگی می‌کنند و با هم پاک می‌شوند.

موجودیت دامنه
عامل‌ها دقیقاً به یک فضای کاری تعلق دارند
مخزن‌ها در یک فضای کاری ثبت می‌شوند (پایین را ببینید)
فضاها، گفتگوها و پیام‌ها محدود به فضای کاری
تیکت‌ها، پروژه‌ها، برنامه‌ها، ارکستراسیون‌ها محدود به فضای کاری
واقعیت‌های حافظه، سیاست‌ها، دامنه‌ها، مجوزهای دسترسی محدود به فضای کاری
اجراهای پایپ‌لاین، قالب‌ها، تریگرها محدود به فضای کاری
وضعیت pull request، فضاهای بازبینی، گروه‌های بازبینی محدود به فضای کاری
گزارش‌های اجرای عامل و حافظهٔ کاری محدود به فضای کاری
اعضا، دعوت‌نامه‌ها، مجوزهای هر مخزن محدود به فضای کاری
نمادها و یال‌های گراف کد با repoId درون فایل فضای کاری محدود می‌شود

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

مخزن‌ها محدود به فضای کاری‌اند؛ هویت میان فضاهای کاری با مسیر است

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

یک ردیف مخزن در فایل پایگاه دادهٔ خودِ فضای کاری‌اش زندگی می‌کند — خود فایل دامنه است، پس جدول هیچ ستون workspaceId و هیچ جدول اتصالی ندارد.

یک مخزن را در یک فضای کاری از تنظیمات → فضای کاری → مخزن‌ها اضافه می‌کنید. ثبت همان checkout در دو فضای کاری، دو ردیف مستقل با دو شناسه می‌سازد؛ هویت مخزن میان فضاهای کاری با مسیر فایل‌سیستم (یا owner/name در GitHub) تعیین می‌شود، هرگز با شناسه.

ثبت سخت‌گیرانه است: مسیر باید درون یک work tree گیت باشد، باید یک ریموت origin داشته باشد و آن ریموت باید به یک forge پشتیبانی‌شده اشاره کند (github.com، gitlab.com یا bitbucket.org). forge از ریموت خوانده و روی مخزن ذخیره می‌شود، پس یک فضای کاری می‌تواند مخزن‌های هر سه را نگه دارد. هر چیز دیگری رد می‌شود. مرورگر پوشه‌ای که از آن انتخاب می‌کنید، فایل‌سیستم میزبان سرور را فهرست می‌کند، محدود به فهرست مجاز --repo-roots سرور (که به‌طور پیش‌فرض پوشهٔ خانهٔ کاربر سرور است).

حذف یک مخزن از یک فضای کاری، ردیف را یکسره پاک می‌کند و نمادهای کد، یال‌ها، فایل‌ها، نقطه‌های بازرسی نمایه و مجوزهای هر عضو آن را آبشاری حذف می‌کند. checkout روی دیسک دست‌نخورده می‌ماند.

گراف کد در همان فایل زندگی می‌کند، با کلید repoId، چون فضاهای کاری مختلف ممکن است همان مخزن را روی شاخه‌های مختلف با کد متفاوت داشته باشند.

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

  • لایهٔ پایگاه داده. هر فضای کاری فایل SQLite خودش را دارد که WorkspaceDatabaseManager.of(workspaceId) آن را تحویل می‌دهد. یک پایگاه دادهٔ فضای کاری، جداول users، workspaces یا هیچ فضای کاری دیگری را اعلان نمی‌کند، پس یک خواندن میان‌فضایی باگی نیست که بتوانید بنویسید — کامپایل نمی‌شود. مخزن‌ها DAO خود را در هر فراخوانی حل می‌کنند نه اینکه یکی را کش کنند، و چون هر متد مخزن یک workspaceId الزامی می‌گیرد، فضای کاری هرگز نمی‌تواند استنتاج یا پیش‌فرض شود.
  • شناسهٔ فضای کاری یک قطعهٔ مسیر است. چون شناسه به نام پوشه تبدیل می‌شود، باید با یک الگوی تک‌قطعه‌ای ایمن مطابقت کند (شروع حرف‌عدد، سپس حرف‌عدد، نقطه، زیرخط، خط تیره؛ بدون ..). شناسه‌ها در همه‌جای محصول UUID هستند. هر چیز دیگری به‌جای پاک‌سازی، خطا پرتاب می‌کند — همان نگهبان پیمایش مسیر است و با صدای بلند شکست می‌خورد تا دادهٔ یک فضای کاری را در مسیری که کس دیگری انتخاب کرده ننویسد.
  • دفاع لایه‌لایه. ستون‌های workspaceId هنوز آنجا هستند و هنوز نوشته می‌شوند. هرچند درون یک فایل زائدند، تریگرهای خوراک همگام‌سازی، نمایه‌های FTS و شکل ردیف‌های موجود را دست‌نخورده نگه می‌دارند و فایل را هنگام بازرسی مستقل، خودتوصیف می‌سازند — چیزی که برون‌بری و درون‌بری به آن تکیه دارند.
  • لایهٔ دامنه. سرویس‌هایی که موجودیت‌ها را با شناسه تغییر می‌دهند، پیش از ادامه entity.workspaceId == workspaceId را در یک نقطهٔ گلوگاه واحد اعتبارسنجی می‌کنند و در صورت ناهم‌خوانی WorkspaceMismatchException پرتاب می‌کنند — همان شکست پرسروصدا برای شناسه‌ای که از بافت اشتباه رسیده است. رد کردن با صدای بلند مهم است: یک no-op بی‌صدا باگ را پنهان می‌کند و ادامه دادن، نشت می‌آورد.
  • لایهٔ RPC. توزیع‌کنندهٔ RPCِ مخزن، نقطهٔ گلوگاه است. هر عملیات محدود به فضای کاری باید workspace_id را در آرگومان‌های خودش داشته باشد — هیچ «فضای کاری جاری» به‌ازای هر نشست وجود ندارد که نشت کند، و شناسهٔ ناشناخته پیش از آنکه چیزی پایگاه داده‌ای باز کند، به‌عنوان پیدا نشد رد می‌شود، پس یک شناسهٔ بد نمی‌تواند یک workspace.db شبح‌وار بسازد. عملیات‌های واقعاً سراسری (خوراک خبری، صف ناوگان) خود را به‌عنوان تصمیمی صریح و بازبینی‌شده بدون دامنه اعلام می‌کنند. ابزارهای MCP از همین قاعده پیروی می‌کنند: هر ابزاری که به داده‌های محدود به فضای کاری دست می‌زند، workspace_id می‌خواهد.
  • پخش میان‌فضایی قابل شمارش است. پاسخ دادن به پرسشی دربارهٔ چند فضای کاری یعنی باز کردن چند پایگاه داده. این پخش در یک کمک‌تابع به نام CrossWorkspaceQueries محدود شده است، تا فهرست کامل چیزهایی که به‌طور مشروع از مرز عبور می‌کنند، در نقاط فراخوانی یک فایل زندگی کند نه آنکه پشت کامنت‌های مستندات پخش شود. آن نقاط فراخوانی عبارت‌اند از: تجمیع سراسری سرور، آشتی‌دهنده‌های راه‌اندازی، جاروب‌های نگه‌داشت و GC، پشتیبان‌گیری، مسیریاب‌های رویداد — و جست‌وجوی عضویت که در پایین می‌آید.
  • دسترسی تنها با شناسه کافی نیست. جست‌وجوی یک موجودیت با UUID آن اثبات نمی‌کند که به فضای کاری فراخوان تعلق دارد. تفکیک فایل، فایل اشتباه را دست‌نیافتنی می‌کند؛ اعتبارسنجی لایهٔ دامنه، شناسهٔ اشتباهی را که از یک فایل مشروع رسیده است می‌گیرد.

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

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

داشتن یک اعتبارنامهٔ دستگاه، یک دستگاه را احراز هویت می‌کند. هیچ چیزی درون فضای کاری را مجاز نمی‌کند. هر عملیات محدود به فضای کاری، نقش عضویت فراخوان را حل می‌کند و کف مجاز را بر اساس نوع عملیات اعمال می‌کند — خواندن guest می‌خواهد، تغییر member و یک عملیات مخرب admin — و عملیاتی که محتوای مخزن را آشکار می‌کند، به‌علاوه مجوز هر مخزن فراخوان را بررسی می‌کند. یک کلید جفت‌سازی معتبر بدون عضویت، پیام Not a member of this workspace می‌گیرد.

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

برای نردبان نقش‌ها و مجوزهای مخزن، چندنفره را ببینید.

نه همه‌چیز محدود به فضای کاری است. یک پایگاه دادهٔ دوم، <dataDir>/global.db، چیزهایی را نگه می‌دارد که واقعاً سراسری سرورند:

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

راه‌اندازی فقط همین فایل را باز می‌کند. فایل‌های فضای کاری در نخستین لمس به‌صورت تنبل باز می‌شوند، پس هزینهٔ شروع ثابت می‌ماند، هر قدر هم که فضاهای کاری تاریخ انباشته باشند، و quick_check هر فایل در نخستین استفاده پرداخت می‌شود نه در مسیر رسیدن به بنر آماده‌بودن.

یک فایل فضای کاری تنها یک workspace_meta تک‌ردیفی برای خودشناسی حمل می‌کند (شناسه‌اش، نصبی که ساخته، نسخهٔ اسکیمایی که با آن ساخته شده، و زمان). هر چیز توصیف‌کنندهٔ یک فضای کاری در رجیستری است.

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

  • یک عامل CEO به همراه چهار متخصص — qa، architect، engineer، librarian — که متخصص‌ها به CEO گزارش می‌دهند؛
  • قالب‌های پایپ‌لاین داخلی و تریگرهایشان (تریگرهای رویدادی و دستی فعال عرضه می‌شوند؛ زمان‌بندی‌شده‌ها به‌جز جاروب روزانهٔ GC worktree اختیاری‌اند)؛
  • مجموعه‌های ارزیابی شروع.

این برای هر فضای کاری رخ می‌دهد، نه فقط اولی. عامل‌های کاشته‌شده بدون آداپتور ساخته می‌شوند، پس روی Control Center (built-in) با مدل پیش‌فرض Anthropic اجرا می‌شوند (claude-opus-4-8 مگر آنکه مدل دیگری را پین کنید) تا وقتی که برای هر عامل در تنظیمات → فضای کاری → عامل‌ها آداپتور و مدل تعیین کنید. مدل عامل را ببینید.

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

حذف یک فضای کاری ردیف رجیستری را علامت می‌زند و پوشه را برمی‌دارد. فضای کاری از هر فهرست و جست‌وجویی ناپدید می‌شود. ردیف رجیستری (حذف‌شدهٔ نرم) باقی می‌ماند تا شناسه هنوز برای پشتیبان‌گیری و نگه‌داشت شناخته‌شده باشد، اما workspace.delete بی‌درنگ پوشهٔ آن فضای کاری — پایگاه داده، worktreeها، فایل‌های عامل و بقیه — را از راه WorkspaceDatabaseManager.dropAndClose حذف می‌کند. تنها مالک فضای کاری می‌تواند آن را حذف کند.

چون یک فضای کاری یک فایل است، دست‌به‌دست کردن یک فضای کاری یک عملیات فایل است نه یک دامپ جدول‌به‌جدول:

  • workspace.export یک VACUUM INTO تکی است — یک اسنپ‌شات سازگار و یکپارچه‌سازی‌شده که زیر یک تراکنش خواندن گرفته می‌شود، در حالی که سرور همچنان سرویس می‌دهد.
  • workspace.import چنین فایلی را می‌پذیرد و هر چیزی را که فضای کاری هدف هم‌اکنون نگه می‌دارد جایگزین می‌کند. workspace_meta جاسازی‌شده به درون‌بری امکان می‌دهد میان «فایل خودم، بازپذیرفته‌شده» و «فایلی از نصبی دیگر» تمایز بگذارد (دومی مجاز است، اما به‌جای نامرئی بودن، ثبت می‌شود).
  • server.backupNow کل نصب را در یک پوشهٔ زمان‌دار اسنپ‌شات می‌گیرد که آینهٔ پوشهٔ دادهٔ زنده است — manifest.json، global.db و یک <workspaceId>/workspace.db به‌ازای هر فضای کاری — پس بازیابی همان کپی کردن آن به عقب است.
  • server.listBackups آن پوشه را به عقب می‌خواند، از تازه‌ترین، با اندازه و فهرست فضاهای کاری هر اسنپ‌شات. اسنپ‌شاتی که مانیفستش گم است یا فایل‌هایی را نام می‌برد که آنجا نیستند، همچنان گزارش می‌شود و ناقص علامت می‌خورد — پنهان کردنش همان راهی است که یک اپراتور به این باور می‌رسد نسخهٔ پشتیبانی دارد که ندارد.

عملیات‌های پشتیبان‌گیری زیر تنظیمات → سرور → پشتیبان و بازیابی قرار دارند: پشتیبان همین حالا از نصب اسنپ‌شات می‌گیرد، فهرست نشان می‌دهد کدام اسنپ‌شات‌ها کامل‌اند و به مالک نصب اجازه می‌دهد پس از تأیید یکی را حذف کند، و هر فضای کاری برون‌بری و درون‌بری عرضه می‌کند. پایپ‌لاین داخلی Workspace backup فقط پایگاه دادهٔ همان فضای کاری را با زمان‌بندی هفتگی می‌گیرد و پشتیبان‌های خودش را که از سن نگه‌داشت پیکربندی‌شده قدیمی‌ترند حذف می‌کند. بازیابی یک فضای کاری از یک اسنپ‌شات نصب، همان workspace.import است که به <workspaceId>/workspace.db اسنپ‌شات اشاره می‌کند؛ بازیابی کل نصب همچنان یک کپی کردن پوشهٔ اسنپ‌شات با سرور متوقف است.

آنچه هیچ‌یک از این‌ها حمل نمی‌کنند، بقیهٔ پوشهٔ فضای کاری است. یک فضای کاری بیش از یک پایگاه داده انباشته می‌کند — تصاویر جای‌گذاری‌شده، فایل‌های مهارت و عامل، اعتبارنامه‌های گفتگو، worktreeها — و برون‌بری و درون‌بری فایل را جابه‌جا می‌کنند، پس هر چیزی کنارش همان‌جا می‌ماند. این برای اعتبارنامه‌ها عامدانه و برای worktreeها یک‌بارمصرف است؛ برای بقیه، محدودیتی است که باید بدانید، نه یک قابلیت. مرجع پشتیبان‌گیری جدول کامل را دارد.

جابه‌جا کردن یک پشتیبان میان سرور و دستگاه شما

بخش با عنوان “جابه‌جا کردن یک پشتیبان میان سرور و دستگاه شما”

هر عملیات بالا به زبان مسیرهایی روی سرور سخن می‌گوید، که فقط وقتی پاسخ کامل است که سرور ماشین خودتان باشد. سه مسیر HTTP امضاشده — /backup/workspace، /backup/snapshot و /backup/restore — بایت‌ها را برای هر توپولوژی دیگری حمل می‌کنند، روی همان خط احراز هویت‌شده‌ای که پروکسی رسانه و مخزن تصویر پیش‌تر استفاده می‌کنند. در صفحه، این یک دکمهٔ دانلود کنار هر برون‌بری و هر اسنپ‌شات است (شما انتخاب می‌کنید کجا فرود بیاید)، و انتخاب فایل و آپلود برای بازیابی از فایلی که روی ماشینی که با آن کار می‌کنید نشسته است. آپلود همانی است که وقتی سرور ماشین شما نیست کار می‌کند: workspace.import می‌خواهد فایل از پیش روی میزبان باشد، و این راه رسیدنش به آنجاست.

سه ویژگی ارزش دانستن دارند:

  • یک دانلود یک نسخهٔ تازه ضرب می‌کند و سرور هیچ‌چیز از آن نگه نمی‌دارد. دانلود کردن یعنی «همان فایلی را بگیر که دکمهٔ برون‌بری ساخت» نیست — دوباره برون‌بری می‌کند، آن را استریم می‌کند و پاکش می‌کند. پاسخ no-store با Accept-Ranges: none است به همان دلیل: یک بازهٔ ازسرگرفته‌شده، دو برون‌بری متفاوت را در یک فایل به هم می‌چسباند که معتبر به نظر می‌رسد و نیست.
  • یک آپلود روی دیسک استریم می‌شود، هرگز بافر نمی‌شود، در هر دو سر، و نسخهٔ آماده‌شده در هر مسیر خروجی — از جمله یک رد — پاک می‌شود. یک پایگاه دادهٔ فضای کاری یک اسکرین‌شات نیست.
  • یک اتصال رله‌شده هیچ مبدأ HTTP ندارد، پس کنترل‌های انتقال آنجا غیرفعال می‌شوند با یادداشتی که دلیلش را می‌گوید. هر چیزی که به زبان مسیرهای سمت سرور سخن می‌گوید روی رله کار می‌کند؛ فقط خطوط بایتی به اتصال مستقیم نیاز دارند.

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

هیچ داشبورد میان‌فضایی وجود ندارد. سطح تحلیل در /workspaces/:workspaceId/observability به یک خوراک سراسری کران‌دار از اجراهای اخیر مشترک می‌شود و بی‌درنگ آن را به فضای کاری فعال محدود می‌کند، پس آنچه آنجا می‌بینید هر بار یک فضای کاری است. تنها سطوح واقعاً چندفضاییکاری که یک انسان لمس می‌کند، انتخاب‌گر فضای کاری و تغییردهنده است.

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