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

مساحات العمل والعزل

مساحة العمل هي مستأجر العزل الأعلى في Control Center. تجمع الوكلاء والمستودعات والمساحات والتذاكر والذاكرة وخطوط الأنابيب والأعضاء في سياق محدود. كل ما يحدث داخلها يبقى داخلها.

مساحة العمل المعتادة تقابل مشروعًا واحدًا أو قاعدة شفرة واحدة. قد تكون لديك مساحات عمل منفصلة لـ «api-service» و«frontend-v2» و«infrastructure».

البيانات على نطاق مساحة العمل

قسم بعنوان “البيانات على نطاق مساحة العمل”

تعيش صفوف مساحة العمل في ملف SQLite الخاص بها، على <dataDir>/<workspaceId>/workspace.db. تحصل كل مساحة عمل على دليل لا على ملف عارٍ، لأنها تجمع أكثر من قاعدة: أشجار عمل لكل محادثة، وملفات الوكيل والمهارات، وبيانات اعتماد بوت الدردشة. إعطاؤها مجلدًا يعني أن ذلك كله يعيش معًا ويُحذف معًا.

الكيان النطاق
الوكلاء ينتمون إلى مساحة عمل واحدة بالضبط
المستودعات تُسجَّل في مساحة عمل واحدة (انظر أدناه)
المساحات والمحادثات والرسائل على نطاق مساحة العمل
التذاكر والمشاريع والخطط والتنسيقات على نطاق مساحة العمل
حقائق الذاكرة والسياسات والنطاقات ومنح الوصول على نطاق مساحة العمل
تشغيلات خطوط الأنابيب والقوالب والمشغّلات على نطاق مساحة العمل
حالة طلب السحب ومساحات المراجعة ومجموعات المراجعة على نطاق مساحة العمل
سجلات تشغيل الوكيل والذاكرة العاملة على نطاق مساحة العمل
الأعضاء والدعوات والمنح لكل مستودع على نطاق مساحة العمل
رموز مخطط الشفرة وحوافه نطاقها repoId داخل ملف مساحة العمل

معظم هذه الصفوف تحمل عمود workspaceId، وملفات القواعد المنفصلة هي التي تفرض العزل. انظر ثابت العزل أدناه.

المستودعات على نطاق مساحة العمل، والهوية عبرها بالمسار

قسم بعنوان “المستودعات على نطاق مساحة العمل، والهوية عبرها بالمسار”

يعيش صف المستودع في ملف قاعدة مساحة عمله. الملف هو النطاق، فلا عمود workspaceId في الجدول ولا جدول ربط.

تضيف مستودعًا إلى مساحة العمل من الإعدادات ← مساحة العمل ← المستودعات. تسجيل نسخة العمل نفسها في مساحتي عمل ينشئ صفين مستقلين بمعرّفين، وهوية المستودع عبر مساحات العمل هي مسار نظام الملفات (أو المالك والاسم على GitHub)، لا المعرّف.

التسجيل صارم: يجب أن يقع المسار داخل شجرة عمل Git، وأن يكون له بعيد origin، وأن يشير ذلك البعيد إلى مضيف شفرة مدعوم (github.com أو gitlab.com أو bitbucket.org). يُقرأ مضيف الشفرة من البعيد ويُخزَّن على المستودع، فتستطيع مساحة عمل واحدة أن تضم مستودعات من الثلاثة. أي شيء آخر يُرفض. يعرض متصفح المجلدات الذي تختار منه نظام ملفات مضيف الخادم، محصورًا بقائمة السماح --repo-roots على الخادم (وافتراضها دليل المنزل لمستخدم الخادم).

إزالة مستودع من مساحة العمل تحذف الصف نهائيًا، ويتتابع الحذف إلى رموزه وحوافه وملفاته ونقاط تفتيش فهرسه ومنحه لكل عضو. نسخة العمل على القرص لا تُمس.

يعيش مخطط الشفرة في الملف نفسه، ومفتاحه repoId، فقد تحمل مساحات العمل المختلفة المستودع نفسه على فروع مختلفة بشفرة مختلفة.

تسريب البيانات عبر مساحات العمل يُعدّ خللًا. العزل بنيوي، ويُفرض على مستويات عدة:

  • طبقة قاعدة البيانات. لكل مساحة عمل ملف SQLite خاص، يسلّمه WorkspaceDatabaseManager.of(workspaceId). قاعدة مساحة العمل لا تصرّح بجداول users ولا workspaces ولا بجداول أي مساحة عمل أخرى، فقراءة عابرة لمساحات العمل ليست خللًا تستطيع كتابته: إنها لا تُترجَم. تحل المستودعات كائن DAO عند كل استدعاء بدل تخزين واحد مؤقتًا، ولأن كل دالة مستودع تأخذ workspaceId مطلوبًا فلا تُستنتج مساحة العمل ولا تُفترض.
  • معرّف مساحة العمل جزء من المسار. لأن المعرّف يصير اسم دليل، لا بد أن يطابق نمطًا آمنًا من مقطع واحد (يبدأ بحرف أو رقم لاتيني، ثم حروف وأرقام لاتينية ونقاط وشرطات سفلية وواصلات، وبلا ..). المعرّفات UUID في المنتج كله. أي شيء آخر يرمي استثناءً بدل أن يُنقَّى. ذلك حارس اجتياز المسار، ويفشل علنًا بدل أن يكتب بيانات مساحة عمل إلى مسار اختاره غيرها.
  • الدفاع في العمق. أعمدة workspaceId ما زالت موجودة وما زالت تُكتَب. هي زائدة داخل الملف، لكنها تبقي مشغّلات تغذية المزامنة وفهارس FTS وأشكال الصفوف القائمة كما هي، وتجعل الملف يصف نفسه حين يُفحَص وحده، وعلى ذلك يعتمد التصدير والاستيراد.
  • طبقة النطاق. الخدمات التي تعدّل الكيانات بالمعرّف تتحقق من entity.workspaceId == workspaceId عند نقطة عبور واحدة قبل أن تمضي، وترمي WorkspaceMismatchException عند الاختلاف. ذلك الرفض العلني لمعرّف وصل من السياق الخطأ. الرفض العلني مهم: التجاهل الصامت يخفي الخلل، والمضي يسرّب.
  • طبقة RPC. موزّع عمليات المستودع هو نقطة العبور. كل عملية على نطاق مساحة العمل تحمل workspace_id في وسائطها. لا توجد «مساحة عمل حالية» لكل جلسة يمكن أن تتسرّب، والمعرّف المجهول يُرفض على أنه غير موجود قبل أن يُفتح أي ملف قاعدة، فلا يستطيع معرّف فاسد أن يُنشئ workspace.db شبحًا. العمليات العامة فعلًا (موجز الأخبار وطابور الأسطول) تعلن نفسها بلا نطاق، بقرار صريح مُراجَع. أدوات MCP تتبع القاعدة نفسها: أي أداة تلمس بيانات على نطاق مساحة العمل تشترط workspace_id.
  • التفرّع عبر مساحات العمل قابل للحصر. الإجابة عن سؤال يخص عدة مساحات عمل تعني فتح عدة قواعد. ذلك التفرّع محصور في مساعد واحد، CrossWorkspaceQueries، فالقائمة الكاملة لما يعبر الحد بصورة مشروعة تعيش في مواضع استدعاء ملف واحد، ولا تتناثر خلف تعليقات التوثيق. تلك المواضع: التجميع على نطاق الخادم، ومُوفِّقات الإقلاع، ومسوح الاحتفاظ وجمع المهملات، والنسخ الاحتياطي، وموجّهات الأحداث، والبحث عن العضوية أدناه.
  • الوصول بالمعرّف وحده لا يكفي. البحث عن كيان بـ UUID لا يثبت أنه يخص مساحة عمل المستدعي. تقسيم الملفات يجعل الملف الخطأ غير قابل للوصول، وتحقق طبقة النطاق يمسك المعرّف الخطأ الذي يصل عبر ملف مشروع.

العضوية تمنح الوصول، لا مفتاح الاقتران

قسم بعنوان “العضوية تمنح الوصول، لا مفتاح الاقتران”

حيازة بيانات اعتماد الجهاز تصادق جهازًا. لا تأذن بشيء داخل مساحة العمل. كل عملية على نطاق مساحة العمل تحل دور عضوية المستدعي وتفرض حدًا أدنى مشتقًا من نوع العملية: القراءة تحتاج guest، والتعديل يحتاج member، والعملية التدميرية تحتاج admin، والعمليات التي تكشف محتوى مستودع تفحص أيضًا منحة المستدعي على ذلك المستودع. مفتاح اقتران صالح بلا عضوية يلقى Not a member of this workspace.

ما يترتب على ذلك للعزل مرتّب: صفوف العضوية تعيش في ملف مساحة العمل نفسها، فسؤال «من في مساحة العمل هذه؟» قراءة عادية ضمن النطاق. السؤال العكسي «في أي مساحات عمل أنا؟» يمتد بالتعريف عبر الملفات، فهو من القليل المسموح به من تفرّعات CrossWorkspaceQueries، وهو ما يشغّله منتقي مساحة العمل قبل أن تُختار أي مساحة.

انظر تعدد اللاعبين لسلّم الأدوار ومنح المستودعات.

ليس كل شيء على نطاق مساحة العمل. قاعدة ثانية، <dataDir>/global.db، تحمل ما يخص الخادم كله فعلًا:

  • سجل مساحات العمل، حتى يسرد مبدّل المساحات كل مساحة عمل من غير أن يفتح ملف مساحة واحد، وحتى يعيش اسم المساحة وشعارها ومالكها وترتيبها اليدوي وعلامة حذفها الناعم خارج الملف الذي تصفه.
  • الهوية: المستخدمون وتفضيلات المستخدم والأجهزة المقترنة، لأن الإنسان الواحد مستخدم واحد عبر كل مساحات العمل، والجهاز المقترن يبقى بعد حذف مساحة عمل.
  • طابور الأسطول (العمال والمهام وسجل الإسناد)، ويطابق مجدوله الطابور كله مع كل عامل في كل نبضة.
  • موجز الأخبار، وهو لكل مستخدم لا لكل مساحة عمل: إضافة تغذية تتبعك إلى كل مساحة عمل.
  • workspace_routes، فهرس ما قبل المصادقة الذي يجيب عن «أي مساحة عمل تملك هذا المفتاح؟» لنقاط الدخول التي لا تصل إلا بسر أو بمعرّف مبهم (تجزئة دعوة، أو رمز webhook، أو رابط عميق) ومن غير مساحة عمل. الغياب هناك غير موجود، ولا رجوع إلى المسح عن قصد.
  • إعدادات التثبيت وهويته: معرّف التثبيت، وإعدادات المضيف، واتصالات SSO، وسياسات الإجراءات المُدارة (قيد على نطاق التثبيت لا يستطيع إلا تضييق ضوابط مساحة العمل).

لا يفتح الإقلاع إلا هذا الملف. تُفتح ملفات مساحات العمل عند أول لمس، فتبقى كلفة البدء ثابتة مهما تراكم تاريخ المساحات، وتُدفَع كلفة quick_check لكل ملف عند أول استخدام لا في الطريق إلى لافتة الجاهزية.

لا يحمل ملف مساحة العمل إلا صفًا واحدًا في workspace_meta ليعرّف نفسه (معرّفه، والتثبيت الذي أنشأه، وإصدار المخطط الذي أُنشئ به، ومتى). كل ما يصف مساحة العمل موجود في السجل.

إنشاء مساحة العمل أكثر من إدراج صف في السجل. في عملية واحدة يختم الخادم المستخدم المنشئ في ownerUserId، ويسجّل عضويته بدور المالك، وينشر حدث النطاق WorkspaceCreated. ثم يبذر المستمعون إلى ذلك الحدث، من غير تكرار وفي الخلفية:

  • وكيل CEO وأربعة متخصصين — qa وarchitect وengineer وlibrarian — يتبعون له.
  • قوالب خطوط الأنابيب المضمّنة ومشغّلاتها (مشغّلات الأحداث واليدوية تصل مفعّلة، والمجدولة اختيارية إلا المسح اليومي لجمع مهملات أشجار العمل).
  • حزم التقييم المبدئية.

يحدث هذا لـ كل مساحة عمل، لا للأولى وحدها. الوكلاء المبذورون يُنشَؤون بلا محوّل، فيعملون على Control Center (built-in) بالنموذج الافتراضي لدى Anthropic (claude-opus-4-8 ما لم تثبّت غيره) إلى أن تضبط محوّلًا ونموذجًا لكل وكيل من الإعدادات ← مساحة العمل ← الوكلاء. انظر نموذج الوكيل.

كتابة عضوية المالك أهم مما تبدو: من غيرها لا يكون لمساحة العمل المنشأة حديثًا أي عضو، ويُرفض كل استدعاء لاحق على نطاق مساحة العمل يصدره من أنشأها.

حذف مساحة العمل يعلّم صف السجل ويفك ارتباط الدليل. تختفي من كل قائمة وكل بحث. يبقى صف السجل (محذوفًا حذفًا ناعمًا) حتى يبقى المعرّف معروفًا للنسخ الاحتياطي والصيانة، لكن workspace.delete يسقط دليل تلك المساحة فورًا — القاعدة وأشجار العمل وملفات الوكيل وسواها — عبر 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 في اللقطة، واستعادة التثبيت كله تبقى نسخ دليل اللقطة بعد إيقاف الخادم.

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

نقل نسخة احتياطية بين الخادم وجهازك

قسم بعنوان “نقل نسخة احتياطية بين الخادم وجهازك”

كل عملية أعلاه تتكلم بمسارات على الخادم، وهذا جواب كافٍ فقط حين يكون الخادم جهازك. ثلاثة مسارات HTTP موقَّعة — /backup/workspace و/backup/snapshot و/backup/restore — تحمل البايتات لكل طوبولوجيا سواها، على الممر المصادَق عليه نفسه الذي يستخدمه أصلًا وسيط الوسائط ومخزن الصور. في الصفحة زر تنزيل بجانب كل تصدير وكل لقطة (تختار أين يستقر)، واختيار ملف ورفعه للاستعادة من ملف على الجهاز الذي تستخدمه. الرفع هو الذي ينفع حين لا يكون الخادم جهازك: يحتاج workspace.import إلى أن يكون الملف على المضيف أصلًا، وهكذا يصل.

ثلاث خصائص تستحق المعرفة:

  • التنزيل يُنتج نسخة جديدة ولا يحتفظ الخادم بشيء منها. التنزيل ليس «اجلب الملف الذي صنعه زر التصدير»: يصدّر من جديد، ويبث الناتج، ثم يحذفه. الاستجابة no-store مع Accept-Ranges: none للسبب نفسه: استئناف نطاق يوصل تصديرين مختلفين في ملف واحد يبدو سليمًا وليس كذلك.
  • يُبث الرفع إلى القرص ولا يُخزَّن في الذاكرة، من الطرفين، وتُحذف النسخة المرحّلة في كل مسار للخروج، والرفض منها. قاعدة مساحة العمل ليست لقطة شاشة.
  • الاتصال عبر المرحّل بلا أصل HTTP، فتُعطَّل عناصر النقل هناك مع ملاحظة تشرح السبب. كل ما يتكلم بمسارات على جهة الخادم يبقى عاملًا عبر المرحّل، وممرات البايت وحدها تحتاج اتصالًا مباشرًا.

تستطيع تشغيل مساحات عمل عدة جنبًا إلى جنب. تعمل كل واحدة باستقلال: وكلاء مساحة لا يراسلون وكلاء أخرى، ولا يقرأون ذاكرتها، ولا يرون تذاكرها. حل المستلم في المراسلة من وكيل إلى وكيل مطابق تمامًا، ولا يعبر مساحة عمل.

لا لوحة تمتد عبر مساحات العمل. سطح التحليلات على /workspaces/:workspaceId/observability يشترك في تغذية عامة محدودة لآخر التشغيلات، ثم يضيّقها فورًا إلى مساحة العمل النشطة، فما تراه هناك مساحة واحدة في كل مرة. السطحان الوحيدان اللذان يلمسهما شخص ويمتدّان فعلًا عبر مساحات العمل هما منتقي مساحة العمل ومبدّل المساحات.

حد عملي يستحق المعرفة: مساحة العمل التي يستخدمها أحد تبقى مفتوحة، بلا إخلاء LRU. إذا تجاوز العدد 32 يحذّر الخادم ولا يرفض، لأن كل ملف مفتوح يحتجز isolate خلفيًا وذاكرة صفحات. التفرّع عبر مساحات العمل يفتح الملفات عابرًا، ويغلقها بعده إن لم يكن شيء آخر قد احتجزها.