Gå til innholdet

Flerbruker — identitet, medlemskap og tilstedeværelse

Control Center fungerer for én operatør, men modellen under det er fleraktør fra første stund. Mennesker og agenter sender meldinger i de samme områdene, redigerer de samme sakene og vises i den samme oversikten. Å gjøre dette trygt og forståelig koker ned til tre beslutninger: hvem som regnes som en aktør, hva som gir tilgang og hvilken tilstand som deles varig, kontra bare kringkastes i sanntid.

Principaler: brukere og agenter er likeverdige aktører

Seksjon med tittelen «Principaler: brukere og agenter er likeverdige aktører»

Alt som registrerer «hvem gjorde dette» — meldinger, saker, gjennomganger, planer, kjøringslogger, revisjonsoppføringer — løses gjennom en Principal, en lukket union av UserPrincipal og AgentPrincipal med en kompakt wire-form (user:<id> / agent:<id>). Attribusjon kollapser aldri N mennesker (eller N agenter) til én sentinelkonto, så en gjennomgang skrevet av en agent og en gjennomgang skrevet av et menneske kan skilles fra hverandre overalt der de dukker opp.

Fordi principalen er identitetsenheten, er den også enheten for inneslutning:

  • Rategrenser er per principal, ikke per økt. Flombeskyttelsen på eksterne verktøykall budsjetterer per bruker, ikke per tilkobling: alle enhetene til et medlem trekker fra ett delt, glidende vindu (120 kall i minuttet, hvorav maks 30 kan mutere), så å åpne tre enheter tredobler ikke noens kvote, og ett støyende medlem kan ikke sulte ut de andre.
  • Angre er per principal. Hver klients handlingsjournal registrerer bare mutasjonene dens principal utførte. Du kan ⌘Z din egen sakeredigering; du kan ikke angre en kollegas — når du ser den, har den varige banen allerede avgjort den (se nedenfor).

Medlemskap er tilgangsgrensen — ikke paringsnøkkelen

Seksjon med tittelen «Medlemskap er tilgangsgrensen — ikke paringsnøkkelen»

To separate legitimasjoner svarer på to separate spørsmål. Paringsnøkkelen (se Fjernstyring og mobil) autentiserer en enhet mot serveren. Medlemskap gir den enhetens bruker rett til å handle inne i et arbeidsområde. En paret enhet hvis bruker ikke har medlemskap i et arbeidsområde, kan verken lese eller mutere det.

Medlemskap er én rad per (arbeidsområde, bruker), lagret i arbeidsområdets egen databasefil — så «hvem er med i dette arbeidsområdet?» er en helt vanlig avgrenset lesing, mens det inverse spørsmålet, «hvilke arbeidsområder er jeg med i?», nødvendigvis spenner over filer og er en av de få godkjente fan-outene på tvers av arbeidsområder. Det er dette arbeidsområdevelgeren kjører før noe arbeidsområde er valgt.

Hver rad har en rolle fra en rangordnet stige med fem nivåer:

Rolle Kan
owner Full kontroll, inkludert sletting av arbeidsområdet; nøyaktig én per arbeidsområde
admin Administrere medlemmer, invitasjoner, innstillinger og destruktive operasjoner
member Daglig lesing og skriving: meldinger, saker, agentkjøringer
viewer Skrivebeskyttet i hele arbeidsområdet; glob-mønstre for hemmelighetsutelatelse skjuler matchende stier på flater med kode
guest Samme lesegulv som leser-rollen, på det laveste trinnet i stigen

Hvert tilgangsvedtak reduseres til «har den som spør, minst rolle X». Gulvet utledes fra operasjonens type med mindre den erklærer ett eksplisitt: en lesing trenger guest, en mutasjon trenger member, en destruktiv operasjon trenger admin. ACL-er per objekt utover repo-tilgangene nedenfor er bevisst utenfor omfanget.

Invitasjoner er måten medlemskap gis på, fra Innstillinger → Arbeidsområde → Medlemmer. En admin lager en engangskode (som en lenke eller QR-kode) som utløper etter sju dager som standard; serveren lagrer bare kodens SHA-256-hash, og selve koden vises nøyaktig én gang. Å løse den inn provisjonerer brukeren JIT, registrerer medlemskapet med invitasjonens rolle (aldri owner — eierskap overføres eksplisitt, gis aldri via invitasjon) og overlater til enhetsparing. Enhetslegitimasjoner som lages etter paring, er selv tidsbegrenset til 30 dager, så en paring er ikke en permanent nøkkel.

Repotilganger ligger under medlemskap. Fordi serveren har fullstendige utsjekker, må medlemskap i arbeidsområdet alene aldri gi mer rettigheter enn kodeverten: hver flate som inneholder kode — filer, differ, PR-gjennomgang, kodesøk — sjekker medlemmets tilgang til det repoet. Tilganger er per (arbeidsområde, bruker, repo) og rangert none → read → review → write, der write betyr å drive agentkjøringer som skriver til repoet. En manglende rad betyr none; eiere og admins har implisitt write på alle tilknyttede repoer og har ingen rader i det hele tatt.

Control Center deler delt tilstand i to baner med motsatte varighetsregler, fordi de to typene tilstand feiler på motsatte måter.

Tilstedeværelse — hvem som er pålogget, hvor de er, hva de skriver, hva en agent gjør akkurat nå — er verdiløs i det øyeblikket den er utdatert. Hvis den ble lagret, måtte hver gjentilkobling avstemme «databasen sier at Sam redigerer denne saken» mot virkeligheten. Derfor skrives tilstedeværelse aldri til databasen. Den lever i en tilstedeværelseshub i minnet på serveren: klienter publiserer med presence.update, andre klienter abonnerer med presence.watch, og oppføringer utløper automatisk etter tre tapte hjerteslag (hjerteslag går hvert 10. sekund; hubben rydder hvert 5.). Det finnes ingenting å avstemme — bare å la falle.

To egenskaper holder banen til å stole på:

  • Identiteten kommer fra økten, aldri fra klientargumenter. Serveren kobler tilkoblingens autentiserte enhet til en bruker-id; en klient kan bare oppdatere sin egen tilstedeværelse, så ingen kan forfalske en annen deltakers status eller posisjon. En eksplisitt offline-oppdatering (frakobling eller den usynlige bryteren) fjerner oppføringen umiddelbart.
  • Oversikten formidles gjennom serveren. Klienter utveksler aldri tilstedeværelse direkte, så alle deltakere ser den samme oversikten uansett topologi. Agenter har ingen klient, så serveren syntetiserer oppføringene deres fra levende kjøringstilstand — tenker, kjører, blokkert på en godkjenningsport, ferdig — med én representativ kjøring som avgjør agentens tilstand og posisjon, slik at samtidige kjøringer ikke får oversikten til å flakke.

Utsendingen er lagdelt per konsument: skrivebord og nett ligger på en full lagdeling (maks én oversiktsutsending per 100 ms), telefonen på en oppsummeringslagdeling (per 500 ms), så oppdateringer i markørtakt ikke smelter PWA-en.

En deltakers posisjon — hvor de befinner seg akkurat nå, helt ned til artefakten — er en lukket union: Space, File (repo + sti + valgfri linje), Pr, Ticket, PlanNode. Ikke alle posisjoner har et sted å hoppe til: det finnes ingen rute for filvisning, så en File-posisjon vises som en etikett i stedet for et reisemål. Banen bærer også myke krav («Sam redigerer denne saken», «en agent holder denne worktreen»): konflikt synlighet, ikke låser. Ukjente posisjonstyper parses til null, så en gammel klient bare lar være å vise det den ikke forstår.

Registre som må overleve — saker, det delte Notater-dokumentet i området, meldinger — tar den motsatte veien: autoritative, ordnede og lagrede. Samtidige redigeringer avgjøres av siste skriver vinner, i den rekkefølgen serveren mottok dem, aldri av klientklokker. Avgjørelsen er bevisst finkornet: sakeredigeringer er per kolonne, så to personer som redigerer ulike felt i samme sak, lander begge, og ingen overskriver den andre (arbeidsflytoverganger beholder optimistisk låsing i stedet); Notater-dokumentet i området er én kolonne, så hele dokumentet er LWW, med myke krav på tilstedeværelsesbanen som gjør samtidig redigering synlig i stedet for låst. Det er bevisst ikke en CRDT — bevisbyrden ligger på CRDT-en, og mottaksrekkefølge-LWW pluss synlige krav dekker de faktiske samarbeidsmønstrene.

«Siste skriver» betyr rekkefølgen serveren mottok dem i, aldri en klientklokke — en bærbar maskin med feil klokke kan ikke vinne en redigering den tapte. Bak den varige banen ligger synkroniseringsstrømmen: SQLite-triggere legger hver mutasjon til en endringslogg per arbeidsområde inne i skrivetransaksjonen, og tildeler et monotont sekvensnummer per arbeidsområde i samme transaksjon som mutasjonen selv. En tjeneste på serveren følger den loggen og lager versjonerte delta-rammer i sekvensrekkefølge. Klienter utvikler seg fra fulle øyeblikksbilder til deltaer; et hull i sekvensen setter det lageret tilbake til øyeblikksbildemodus i stedet for å bruke rammer feil.

Tilstedeværelsesbanen er ikke bare en oversikt — den driver samarbeidsverbene:

  • Følg-modus. Klikk på en deltaker, og klienten din rir på posisjonen deres og navigerer mens de navigerer. Følg varer «til du handler»: i det øyeblikket du navigerer på egen hånd, kobles følgen fra. Et banner som kan avvises, varsler når en annen deltaker setter søkelys på (presenterer) et område du er i.
  • Styr. En melding til en kjørende agent kølegges som styring; agenten leser den på nytt før den fortsetter på neste tur, så du kan dytte en kjøring i riktig retning underveis uten å drepe den.
  • Ta over. Å overta kontrollen over en samtale setter hver aktive kjøring på pause ved neste rene tur-grense (en Claude Code-kjøring, som ikke har noen trygg grense, stoppes) bak et git-øyeblikksbilde per tur, så overtakelsen kan reverseres. En varig markør blokkerer nye utsendinger inn i området og overlever en serveromstart — serveren gjenopptar aldri automatisk inn i et menneskes halvferdige redigering. Én levende redaktør per worktree er et mykt krav: en andre overtakelse er synlig og avvises i stedet for å slås sammen i det stille.
  • Gi tilbake. Å gi tilbake kontrollen legger ut et strukturert diff-sammendrag av worktreen i området, kølegger det som styring for de pausede kjøringene, gjenopptar dem og fjerner markøren.

Styring og overtakelse er inngrep i etterkant. Autonomibryteren er den varige innstillingen: per område, per agent, satt fra områdehodet, og den avgjør hva som skjer når en agent når en handling bak en port.

Nivå Wire-verdi Effekt på et verktøykall bak en port
Bare foreslå proposeOnly Nektes umiddelbart, med en begrunnelse agenten bes formidle videre — den kan beskrive handlingen, men ikke utføre den
Handle med godkjenning actWithApproval Godkjenningsporten som feiler lukket: én bekreftelse vises til en tilkoblet operatør, og ingen godkjenner betyr nektet
Handle fritt actFreely Forhåndsgodkjent, unntatt der en hard deny-regel i tillatelsesreglene gjelder — en nekt-regel vinner alltid
(ikke satt) null Samme som handle med godkjenning: serverens standard

To ting ligger utenfor bryteren i begge retninger. En modus’ eget utdataverb er låst og kan aldri nektes, uansett hva bryteren sier — orkestreringsmodus var en gang strukturelt ute av stand til å sende fra seg det eneste den leverer, fordi et forhåndsvalg nektet den effektklassen verbet erklærer. Og en hard deny fra handlingstillatelsesreglene kan ikke overstyres av actFreely.

Alt dette er additivt. Arbeidsflyten til en som jobber alene, er uendret: med ett menneske og ingen agenter har tilstedeværelseshubben ingenting å kringkaste, så banen sender sitt første øyeblikksbilde og ligger deretter i ro med null trafikk; LWW- og synkroniseringsmaskineriet er den samme enkelt-skriver-banen som det alltid har vært; og paringsnøkkelen er fortsatt enhetslegitimasjonen den alltid har vært. Medlemskap er allerede tilgangsgrensen — med ett menneske finnes det én eierrad og ingen ekstra grensesnittelementer for oversikten.