Спільна робота — ідентичність, членство та присутність
Control Center працює і для одного оператора, але модель під ним багатоакторна від самого початку. Люди й агенти пишуть повідомлення в тих самих просторах, редагують ті самі тікети й з’являються в тому самому списку учасників. Щоб це було безпечним і зрозумілим, потрібні три рішення: хто вважається актором, що визначає доступ і який стан зберігається надійно, а який лише транслюється наживо.
Принципали: користувачі й агенти — рівноправні актори
Розділ із назвою «Принципали: користувачі й агенти — рівноправні актори»Усе, що фіксує «хто це зробив» — повідомлення, тікети, перевірки, плани,
журнали запусків, записи аудиту, — визначається через принципал, замкнене
об’єднання UserPrincipal та AgentPrincipal з компактним дротовим форматом
(user:<id> / agent:<id>). Атрибуція ніколи не зводить N людей (чи N
агентів) до одного сторожового облікового запису, тож перевірку, залишену
агентом, і перевірку, залишену людиною, можна розрізнити всюди, де вони
з’являються.
Оскільки принципал — це одиниця ідентичності, він також одиниця стримування:
- Обмеження швидкості — на принципал, а не на сеанс. Захист від флуду для віддалених викликів інструментів розподіляє бюджет за користувачем, а не за з’єднанням: усі пристрої учасника черпають з одного спільного ковзного вікна (120 викликів за хвилину, з яких щонайбільше 30 можуть змінювати стан), тож відкриття трьох пристроїв не потроює квоту нікому, і один гамірний учасник не заморить голодом решту.
- Скасування — на принципал. Журнал дій кожного клієнта фіксує лише ті зміни, які виконав його принципал. Ви можете скасувати ⌘Z власну правку тікета, але не правку колеги — на момент, коли ви її бачите, надійний канал уже її розв’язав (див. нижче).
Членство — межа доступу, а не ключ прив’язки
Розділ із назвою «Членство — межа доступу, а не ключ прив’язки»Два різні облікові дані відповідають на два різні питання. Ключ прив’язки (див. Віддалене керування та мобільний клієнт) автентифікує пристрій на сервері. Членство дає користувачу цього пристрою право діяти в робочому просторі. Прив’язаний пристрій, користувач якого не має членства в робочому просторі, не може ні читати його, ні змінювати.
Членство — це один рядок на пару (робочий простір, користувач), збережений у власному файлі бази даних цього робочого простору — тож питання «хто є в цьому робочому просторі?» — це звичайне читання в межах однієї області, а обернене питання «у яких робочих просторах я перебуваю?» неминуче охоплює кілька файлів і є одним із небагатьох санкціонованих розгалужень між робочими просторами. Саме його виконує вибір робочого простору, перш ніж жоден робочий простір ще вибрано.
Кожен рядок несе роль із п’ятирівневої драбини, упорядкованої за рангом:
| Роль | Що може |
|---|---|
owner |
Повний контроль, включно з видаленням робочого простору; рівно один на робочий простір |
admin |
Керування учасниками, запрошеннями, налаштуваннями, руйнівними операціями |
member |
Повсякденне читання й запис: повідомлення, тікети, запуски агентів |
viewer |
Лише читання в усьому робочому просторі; глоби виключення секретів ховають відповідні шляхи на поверхнях із кодом |
guest |
Така сама межа лише читання, як у переглядача, на найнижчому рангі драбини |
Кожне рішення про доступ зводиться до «чи роль того, хто звертається,
щонайменше X». Мінімальна межа виводиться з виду операції, якщо вона не
оголошує її явно: читання потребує guest, зміна — member, руйнівна
операція — admin. ACL на окремі об’єкти, крім дозволів на репозиторій,
описаних нижче, навмисно поза межами.
Запрошення — це те, як надають членство, з розділу Налаштування → Робочий
простір → Учасники. Адмін створює одноразовий код (як посилання або QR), який
за замовчуванням спливає через сім днів; сервер зберігає лише SHA-256-хеш
коду, а сам код показується рівно один раз. Погашення коду JIT-провізіює
користувача, записує членство з роллю із запрошення (ніколи owner —
володіння передають явно, його ніколи не надають запрошенням) і передає
керування прив’язуванню пристрою. Облікові дані пристрою, створені після
прив’язування, самі обмежені 30 днями, тож прив’язування не є вічним ключем.
Дозволи на репозиторій лежать під членством. Оскільки сервер має повні
робочі копії, саме лише членство в робочому просторі ніколи не повинно давати
більше прав, ніж forge: кожна поверхня, що працює з кодом, — файли, діфи,
перевірка PR, пошук коду — перевіряє дозвіл учасника саме на цей
репозиторій. Дозволи задаються на комбінацію (робочий простір, користувач,
репозиторій) і мають ранги none → read → review → write, де write
означає запуск агентів, які пишуть у репозиторій. Відсутність рядка означає
none; власники й адміни неявно мають write на кожен пов’язаний репозиторій
і не мають жодних рядків.
Два канали стану спільної роботи
Розділ із назвою «Два канали стану спільної роботи»Control Center розділяє спільний стан на два канали з протилежними правилами надійності, бо ці два види стану відмовляють по-різному.
Ефемерний канал присутності
Розділ із назвою «Ефемерний канал присутності»Присутність — хто в мережі, де він, що набирає, чим саме зараз займається
агент — нічого не варта тієї ж миті, коли застаріває. Якби її зберігали, кожне
повторне з’єднання мусило б узгоджувати «база даних каже, що Сем редагує цей
тікет» із реальністю. Тож присутність ніколи не записується в базу даних.
Вона живе в хабі присутності в пам’яті на сервері: клієнти публікують
через presence.update, інші клієнти підписуються через presence.watch, а
записи автоматично спливають після трьох пропущених сигналів (сигнали
надходять кожні 10 секунд; хаб прибирає їх кожні 5). Узгоджувати тут нічого —
лише дати йому згаснути.
Дві властивості роблять цей канал надійним:
- Ідентичність береться із сеансу, ніколи з аргументів клієнта. Сервер
зіставляє автентифікований пристрій з’єднання з ідентифікатором користувача;
клієнт може оновлювати лише власну присутність, тож ніхто не може підробити
статус чи локус іншого учасника. Явне оновлення
offline(від’єднання або перемикач невидимості) негайно видаляє запис. - Список учасників формує сервер. Учасники ніколи не обмінюються присутністю напряму, тож кожен бачить той самий список незалежно від топології. Агенти не мають клієнта, тож сервер синтезує їхні записи з живого стану запусків — думає, працює, заблокований на шлюзі підтвердження, завершив — причому стан і локус агента визначає один репрезентативний запуск, щоб паралельні запуски не змушували список учасників блимати.
Розгалуження поділяється на рівні за споживачем: десктоп і веб користуються повним рівнем (щонайбільше одне надсилання списку учасників на 100 мс), телефон — зведеним (раз на 500 мс), тож оновлення з частотою руху курсора не розтоплять PWA.
Локус учасника — де він зараз перебуває, аж до артефакту — це замкнене
об’єднання: Space, File (репозиторій + шлях + необов’язковий рядок), Pr,
Ticket, PlanNode. Не кожен локус має куди перейти: маршруту для переглядача
файлів немає, тож локус File відображається як напис, а не як пункт
призначення. Канал також несе м’які заявки («Сем редагує цей тікет»,
«агент займає цей worktree»): видимість конфліктів, а не блокування.
Невідомі види локусів розбираються як null, тож старий клієнт просто не
відображає того, чого не розуміє.
Надійний спільний канал
Розділ із назвою «Надійний спільний канал»Записи, які мусять пережити все — тікети, спільний документ «Нотатки» простору, повідомлення — ідуть протилежним шляхом: авторитетні, впорядковані та збережені. Паралельні правки розв’язуються за принципом перемагає останній запис у порядку надходження на сервер, ніколи за годинниками клієнтів. Розв’язання навмисно дрібнозернисте: правки тікетів — поколонкові, тож двоє людей, які редагують різні поля одного тікета, обидва зберігаються, і ніхто не затирає іншого (переходи робочого процесу натомість зберігають оптимістичне блокування); документ «Нотатки» простору — це один стовпець, тож увесь документ підпадає під LWW, а м’які заявки в каналі присутності роблять паралельне редагування видимим, а не заблокованим. Це навмисно не CRDT — тягар доведення лежить на CRDT, а LWW за порядком надходження разом із видимими заявками покриває реальні моделі спільної роботи.
«Останній запис» означає порядок надходження на сервер, ніколи не годинник клієнта — ноутбук із перекошеним годинником не може виграти правку, яку програв. За надійним каналом стоїть стрічка синхронізації: тригери SQLite дописують кожну зміну до журналу змін робочого простору всередині транзакції запису, виділяючи монотонний порядковий номер робочого простору в тій самій транзакції, що й саму зміну. Сервіс на сервері читає цей журнал і перетворює його на версіоновані кадри дельт у порядку номерів. Клієнти переходять від повних знімків до дельт; розрив у послідовності повертає це сховище до режиму знімків, а не застосовує кадри неправильно.
Стеження, коригування, перехоплення, повернення
Розділ із назвою «Стеження, коригування, перехоплення, повернення»Канал присутності — це не просто список учасників: він приводить у дію дієслова спільної роботи:
- Режим стеження. Натисніть на учасника — і ваш клієнт рухається за його локусом, переходячи туди, куди переходить він. Стеження триває «до вашої першої дії»: щойно ви переходите кудись самі, стеження відчіплюється. Банер, який можна закрити, повідомляє, коли інший учасник презентує простір, у якому ви перебуваєте.
- Коригування. Повідомлення агенту, що працює, стає в чергу як коригування; агент перечитує його, перш ніж продовжити наступний хід, тож ви можете підштовхнути запуск на ходу, не вбиваючи його.
- Перехоплення. Перебирання контролю над розмовою зупиняє кожен активний запуск на його наступній чистій межі ходу (запуск Claude Code, який не має безпечної межі, зупиняється) із знімком git на кожен хід, тож перехоплення можна відкотити. Надійна позначка блокує нові запуски в простір і переживає перезапуск сервера — сервер ніколи не відновлює роботу автоматично посеред незавершеної правки людини. Один живий редактор на worktree — це м’яка заявка: друге перехоплення видно, і йому відмовляють, а не зливають його мовчки.
- Повернення. Повертаючи контроль, ви публікуєте в просторі структурований підсумок diff для worktree, він стає в чергу як коригування для призупинених запусків, вони відновлюються, а позначка знімається.
Перемикач автономності
Розділ із назвою «Перемикач автономності»Коригування й перехоплення — це втручання постфактум. Перемикач автономності — це постійне налаштування: окремо для кожного простору й кожного агента, задається в заголовку простору й визначає, що станеться, коли агент доходить до дії під шлюзом.
| Рівень | Дротове значення | Вплив на виклик інструмента під шлюзом |
|---|---|---|
| Лише пропонувати | proposeOnly |
Відхиляється одразу, з причиною, яку агент має передати далі — він може описати дію, але не виконати її |
| Діяти з підтвердженням | actWithApproval |
Шлюз підтвердження, що відмовляє за замовчуванням: одне підтвердження надходить під’єднаному оператору, а якщо того, хто підтверджує, немає — відмова |
| Діяти вільно | actFreely |
Попередньо схвалено, крім випадків, коли діє жорстке правило deny з обмежень — правило заборони завжди перемагає |
| (не задано) | null |
Те саме, що «діяти з підтвердженням»: типове значення сервера |
Дві речі стоять поза перемикачем в обидва боки. Власне вихідне дієслово режиму
закріплене, і йому ніколи не можна відмовити, хоч би що казав перемикач —
колись режим оркестрації структурно не міг видати свій єдиний результат, бо
пресет заборонив клас ефектів, який оголошує це дієслово. А жорстке deny від
обмежень дій не можна перекрити через actFreely.
Робота наодинці без регресій
Розділ із назвою «Робота наодинці без регресій»Усе це — надбудова. Робочий процес оператора-одинака не змінюється: коли є одна людина й жодного агента, хабу присутності нічого транслювати, тож канал надсилає початковий знімок і далі простоює без трафіку; механізми LWW і синхронізації — це той самий шлях з єдиним записувачем, яким він завжди був; а ключ прив’язки залишається обліковими даними пристрою, якими він завжди був. Членство вже є межею доступу — з однією людиною є єдиний рядок власника і жодного інтерфейсного оздоблення для списку учасників.
Пов’язані поняття
Розділ із назвою «Пов’язані поняття»- Робочі простори та ізоляція: межа, яку забезпечує членство
- Віддалене керування та мобільний клієнт: ключ прив’язки й облікові дані пристрою як бік ідентичності
- Модель агента: агентська половина об’єднання принципалів
- Життєвий цикл запуску агента: запуски, які зупиняють і відновлюють коригування та перехоплення
- Обмеження: політика класів ефектів, з якою поєднується перемикач автономності
- Доменні події: шина, якою рухаються живі оновлення