Multiplayer — identitate, apartenență și prezență
Control Center funcționează pentru un singur operator, dar modelul de sub el este multi-actor de la bun început. Oamenii și agenții trimit mesaje în aceleași spații, editează aceleași tichete și apar în aceeași listă de participanți. Ca să fie sigur și ușor de înțeles, totul se reduce la trei decizii: cine contează ca actor, ce controlează accesul și care stare este partajată durabil, spre deosebire de cea doar difuzată în direct.
Principalii: utilizatorii și agenții sunt actori cu statut egal
Secțiune intitulată „Principalii: utilizatorii și agenții sunt actori cu statut egal”Tot ce consemnează „cine a făcut asta” — mesaje, tichete, revizuiri, planuri,
jurnale de rulare, înregistrări de audit — se rezolvă printr-un Principal,
o uniune sigilată din UserPrincipal și AgentPrincipal, cu o formă compactă
de transmisie (user:<id> / agent:<id>). Atribuirea nu colapsează niciodată
N oameni (sau N agenți) într-un singur cont santinelă, așa că o revizuire
lăsată de un agent și una lăsată de o persoană se disting oriunde apar.
Deoarece principalul este unitatea de identitate, el este și unitatea de izolare:
- Limitele de rată sunt per principal, nu per sesiune. Protecția împotriva inundării pentru apelurile de instrumente la distanță bugetează pe utilizator, nu pe conexiune: toate dispozitivele unui membru trag dintr-o singură fereastră glisantă partajată (120 de apeluri pe minut, dintre care cel mult 30 pot modifica starea), așa că deschiderea a trei dispozitive nu triplează cota nimănui, iar un membru zgomotos nu îi poate înfometa pe ceilalți.
- Anularea este per principal. Jurnalul de acțiuni al fiecărui client înregistrează doar mutațiile efectuate de propriul său principal. Poți aplica ⌘Z propriei modificări de tichet; nu poți anula modificarea unui coleg — până când o vezi, calea durabilă a rezolvat-o deja (vezi mai jos).
Apartenența este granița de acces — nu cheia de asociere
Secțiune intitulată „Apartenența este granița de acces — nu cheia de asociere”Două credențiale separate răspund la două întrebări separate. Cheia de asociere (vezi Control remote și mobil) autentifică un dispozitiv la server. Apartenența autorizează utilizatorul acelui dispozitiv să acționeze în interiorul unui spațiu de lucru. Un dispozitiv asociat al cărui utilizator nu deține nicio apartenență într-un spațiu de lucru nu îl poate nici citi, nici modifica.
Apartenența este o linie pentru fiecare pereche (spațiu de lucru, utilizator), stocată în fișierul de bază de date propriu acelui spațiu de lucru — așa că „cine este în acest spațiu de lucru?” este o citire obișnuită cu domeniu limitat, în timp ce întrebarea inversă, „în ce spații de lucru sunt?”, se întinde inevitabil peste mai multe fișiere și este una dintre puținele difuzări aprobate între spații de lucru. Este ce rulează selectorul de spații de lucru înainte ca vreun spațiu de lucru să fi fost ales.
Fiecare linie poartă un rol dintr-o scară de cinci niveluri, ordonate după rang:
| Rol | Poate |
|---|---|
owner |
Control complet, inclusiv ștergerea spațiului de lucru; exact unul per spațiu de lucru |
admin |
Gestionează membri, invitații, setări, operațiuni distructive |
member |
Citire-scriere de zi cu zi: mesaje, tichete, rulări de agenți |
viewer |
Doar citire în tot spațiul de lucru; șabloanele glob de excludere a secretelor ascund căile corespunzătoare pe suprafețele cu cod |
guest |
Același prag minim de doar citire ca viewer, la cel mai de jos rang al scării |
Fiecare decizie de acces se reduce la „rolul apelantului este cel puțin X?”.
Pragul minim este derivat din tipul operațiunii, dacă aceasta nu declară unul
în mod explicit: o citire necesită guest, o mutație necesită member, o
operațiune distructivă necesită admin. ACL-urile per obiect, dincolo de
permisiunile per repo descrise mai jos, sunt în mod deliberat în afara
domeniului.
Invitațiile sunt modalitatea prin care se acordă apartenența, din Setări →
Spațiu de lucru → Membri. Un admin emite un cod de unică folosință (sub formă
de link sau cod QR) care expiră implicit după șapte zile; serverul stochează
doar hash-ul SHA-256 al codului, iar codul în sine este afișat o singură dată.
Utilizarea lui provisionează utilizatorul pe loc (JIT), înregistrează
apartenența la rolul invitației (niciodată owner — proprietatea se transferă
explicit, niciodată prin invitație) și predă ștafeta către asocierea
dispozitivului. Credențialele de dispozitiv emise după asociere sunt ele însele
limitate la 30 de zile, așa că o asociere nu este o cheie permanentă.
Permisiunile per repo se află sub apartenență. Deoarece serverul deține
copii de lucru complete, apartenența la spațiul de lucru nu trebuie niciodată
să acorde, singură, mai multe privilegii decât forge-ul: fiecare suprafață cu
cod — fișiere, diff-uri, revizuirea PR, căutarea de cod — verifică permisiunea
membrului pe acel repo. Permisiunile sunt per (spațiu de lucru, utilizator,
repo) și ordonate none → read → review → write, unde write înseamnă a
dirija rulări de agenți care scriu în repo. Absența unei linii înseamnă
none; proprietarii și administratorii dețin implicit write pe fiecare repo
conectat și nu au nicio linie.
Două căi pentru starea de colaborare
Secțiune intitulată „Două căi pentru starea de colaborare”Control Center împarte starea partajată în două căi cu reguli de durabilitate opuse, pentru că cele două tipuri de stare eșuează în moduri opuse.
Calea efemeră a prezenței
Secțiune intitulată „Calea efemeră a prezenței”Prezența — cine este online, unde se află, ce tastează, ce face un agent chiar
acum — nu valorează nimic în momentul în care devine învechită. Dacă ar fi
persistată, fiecare reconectare ar trebui să reconcilieze „baza de date spune
că Sam editează acest tichet” cu realitatea. Așa că prezența nu este
niciodată scrisă în baza de date. Ea trăiește într-un hub de prezență
din memoria serverului: clienții publică cu presence.update, partenerii se
abonează cu presence.watch, iar intrările expiră automat după trei bătăi de
inimă ratate (bătăile de inimă au loc la fiecare 10 secunde; hub-ul face
curățenie la fiecare 5). Nu este nimic de reconciliat — doar de lăsat să
expire.
Două proprietăți mențin calea demnă de încredere:
- Identitatea vine din sesiune, niciodată din argumentele clientului.
Serverul asociază dispozitivul autentificat al conexiunii cu un id de
utilizator; un client nu poate actualiza niciodată decât propria prezență,
așa că nimeni nu poate falsifica starea sau locus-ul altui participant. O
actualizare explicită
offline(deconectare sau comutatorul de invizibilitate) elimină intrarea imediat. - Lista de participanți trece prin hub-ul serverului. Partenerii nu fac schimb de prezență direct, așa că fiecare participant vede aceeași listă indiferent de topologie. Agenții nu au client, așa că serverul le sintetizează intrările din starea rulărilor în direct — se gândește, rulează, blocat la o poartă de aprobare, gata — o singură rulare reprezentativă hotărând starea și locus-ul unui agent, pentru ca rulările concurente să nu facă lista să oscileze.
Difuzarea este stratificată pe consumator: desktopul și webul merg pe un nivel complet (cel mult o emisiune a listei la fiecare 100 ms), telefonul pe un nivel de sinteză (la fiecare 500 ms), astfel încât actualizările la cadența cursorului să nu topească PWA-ul.
Locus-ul unui participant — unde se află în acest moment, până la nivel de
artefact — este o uniune sigilată: Space, File (repo + cale + linie
opțională), Pr, Ticket, PlanNode. Nu orice locus are undeva unde să
sari: nu există o rută de vizualizare a fișierelor, așa că un locus File se
afișează ca etichetă, nu ca destinație. Calea transportă și revendicări
neangajante („Sam editează acest tichet”, „un agent deține acest worktree”):
vizibilitate a conflictelor, nu blocaje. Tipurile de locus necunoscute se
interpretează ca null, așa că un client vechi pur și simplu nu afișează ce nu
înțelege.
Calea durabilă partajată
Secțiune intitulată „Calea durabilă partajată”Înregistrările care trebuie să supraviețuiască — tichete, documentul Notes partajat al spațiului, mesaje — urmează calea opusă: autoritativă, ordonată și persistată. Modificările concurente se rezolvă prin ultimul scriitor câștigă, în ordinea de primire la server, niciodată după ceasurile clienților. Rezolvarea este în mod deliberat cu granulație fină: modificările de tichet sunt pe coloană, așa că doi oameni care editează câmpuri diferite ale aceluiași tichet ajung amândoi, fără ca vreunul să îl calce pe celălalt (tranzițiile de flux de lucru păstrează în schimb blocarea optimistă); documentul Notes al spațiului este o singură coloană, așa că întregul document urmează LWW, iar revendicările neangajante de pe calea de prezență fac editarea concurentă vizibilă în loc să o blocheze. Nu este în mod deliberat un CRDT — sarcina probei revine CRDT-ului, iar LWW după ordinea de primire plus revendicările vizibile acoperă tiparele reale de colaborare.
„Ultimul scriitor” înseamnă ordinea de primire la server, niciodată un ceas de client — un laptop cu ceasul decalat nu poate câștiga o modificare pe care a pierdut-o. În spatele căii durabile se află fluxul de sincronizare: declanșatoarele SQLite adaugă fiecare mutație într-un jurnal de modificări propriu spațiului de lucru în interiorul tranzacției de scriere, alocând un număr de secvență monoton, propriu spațiului de lucru, în aceeași tranzacție cu mutația însăși. Un serviciu de pe server urmărește acel jurnal și îl transformă în cadre delta versionate, în ordinea secvenței. Clienții evoluează de la instantanee complete la delta; o lacună în secvență face ca acel depozit să revină în modul instantaneu, în loc să aplice greșit cadrele.
Urmărire, direcționare, preluare a controlului, predare înapoi
Secțiune intitulată „Urmărire, direcționare, preluare a controlului, predare înapoi”Calea de prezență nu este doar o listă de participanți — ea conduce verbele de colaborare:
- Modul Urmărire. Dă clic pe un participant și clientul tău călărește locus-ul acestuia, navigând pe măsură ce navighează. Urmărirea este „până când acționezi”: în momentul în care navighezi din proprie inițiativă, urmărirea se detașează. Un banner care poate fi închis anunță când un alt participant evidențiază (prezintă) un spațiu în care te afli.
- Direcționare. Un mesaj adresat unui agent în rulare este pus în coadă ca direcționare; agentul îl recitește înainte de a-și relua următorul tur, așa că poți corecta o rulare în plin zbor fără să o omori.
- Preluare a controlului. Preluarea controlului asupra unei conversații pune în pauză fiecare rulare activă la următoarea limită curată de tur (o rulare Claude Code, care nu are o limită sigură, este oprită), în spatele unui instantaneu git pentru fiecare tur, astfel încât preluarea este reversibilă. Un marcaj durabil blochează trimiterile noi în spațiu și supraviețuiește unei reporniri a serverului — serverul nu reia niciodată automat peste modificarea neterminată a unui om. Un singur editor activ per worktree este o revendicare neangajantă: o a doua preluare este vizibilă și refuzată, nu fuzionată în tăcere.
- Predare înapoi. Returnarea controlului publică în spațiu un rezumat structurat al diff-ului worktree-ului, îl pune în coadă ca direcționare pentru rulările în pauză, le reia și șterge marcajul.
Cadranul de autonomie
Secțiune intitulată „Cadranul de autonomie”Direcționarea și preluarea controlului sunt intervenții ulterioare. Cadranul de autonomie este setarea permanentă: per spațiu, per agent, stabilită din antetul spațiului, el decide ce se întâmplă când un agent ajunge la o acțiune supusă unei porți.
| Nivel | Valoare transmisă | Efect asupra unui apel de instrument supus porții |
|---|---|---|
| Doar propune | proposeOnly |
Refuzat direct, cu un motiv pe care agentul este instruit să îl transmită — poate descrie acțiunea, dar nu o poate executa |
| Acționează cu aprobare | actWithApproval |
Poarta de aprobare cu refuz implicit: o confirmare ajunge la un operator conectat, iar lipsa unui aprobator înseamnă refuz |
| Acționează liber | actFreely |
Preaprobat, cu excepția cazului în care se aplică o regulă dură deny de guardrail — o regulă de refuz câștigă întotdeauna |
| (nesetat) | null |
La fel ca acționează-cu-aprobare: valoarea implicită a serverului |
Două lucruri rămân în afara cadranului, în ambele direcții. Verbul de ieșire
propriu unui mod este fixat și nu poate fi niciodată refuzat, orice ar spune
cadranul — modul orchestrate a fost cândva structural incapabil să emită
singurul său livrabil, pentru că o presetare refuza clasa de efecte pe care o
declară acel verb. Iar un deny dur venit de la guardrail-urile de acțiune nu
poate fi anulat de actFreely.
Zero regresii în regim solo
Secțiune intitulată „Zero regresii în regim solo”Toate acestea sunt aditive. Fluxul de lucru al unui operator solo rămâne neschimbat: cu un singur om și niciun agent, hub-ul de prezență nu are ce să difuzeze, așa că calea emite instantaneul său inițial și apoi stă degeaba, cu trafic zero; mecanismul LWW și de sincronizare este aceeași cale cu un singur scriitor de totdeauna; iar cheia de asociere rămâne credențialul de dispozitiv care a fost dintotdeauna. Apartenența este deja granița de acces — cu un singur om există o singură linie de proprietar și niciun element de interfață pentru listă.
Concepte conexe
Secțiune intitulată „Concepte conexe”- Spații de lucru și izolare: granița pe care o impune apartenența
- Control remote și mobil: latura de identitate a cheii de asociere și a credențialului de dispozitiv
- Modelul agenților: jumătatea de agent a uniunii de principal
- Ciclul de viață al trimiterii agenților: rulările pe care direcționarea și preluarea controlului le pun în pauză și le reiau
- Guardrail-uri: politica de clase de efecte cu care se compune cadranul de autonomie
- Evenimente de domeniu: magistrala pe care circulă actualizările în timp real