Μετάβαση στο περιεχόμενο

Συνεργασία — ταυτότητα, συμμετοχή και παρουσία

Το Control Center λειτουργεί για έναν μόνο χειριστή, αλλά το μοντέλο από κάτω είναι πολυ-δρωντικό από την αρχή. Άνθρωποι και πράκτορες ανταλλάσσουν μηνύματα στους ίδιους χώρους, επεξεργάζονται τα ίδια εισιτήρια και εμφανίζονται στον ίδιο κατάλογο. Το να γίνει αυτό ασφαλές και κατανοητό ανάγεται σε τρεις αποφάσεις: ποιος μετράει ως δρων, τι ελέγχει την πρόσβαση και ποια κατάσταση μοιράζεται ανθεκτικά και ποια απλώς μεταδίδεται ζωντανά.

Εντολείς: χρήστες και πράκτορες είναι ισότιμοι δρώντες

Ενότητα με τίτλο «Εντολείς: χρήστες και πράκτορες είναι ισότιμοι δρώντες»

Ό,τι καταγράφει «ποιος το έκανε αυτό» — μηνύματα, εισιτήρια, κριτικές, πλάνα, αρχεία καταγραφής εκτελέσεων, καταχωρίσεις ελέγχου — περνά από έναν εντολέα, μια σφραγισμένη ένωση των UserPrincipal και AgentPrincipal με συμπαγή μορφή μετάδοσης (user:<id> / agent:<id>). Η απόδοση δεν συμπτύσσει ποτέ N ανθρώπους (ή N πράκτορες) σε έναν λογαριασμό-φρουρό, οπότε μια κριτική που άφησε ένας πράκτορας και μια κριτική που άφησε ένας άνθρωπος ξεχωρίζουν παντού όπου εμφανίζονται.

Επειδή ο εντολέας είναι η μονάδα ταυτότητας, είναι και η μονάδα περιορισμού:

  • Τα όρια ρυθμού είναι ανά εντολέα, όχι ανά συνεδρία. Ο φύλακας κατάκλυσης για τις απομακρυσμένες κλήσεις εργαλείων υπολογίζει ανά χρήστη, όχι ανά σύνδεση: όλες οι συσκευές ενός μέλους αντλούν από ένα κοινό κυλιόμενο παράθυρο (120 κλήσεις το λεπτό, από τις οποίες το πολύ 30 επιτρέπεται να μεταβάλλουν), οπότε το άνοιγμα τριών συσκευών δεν τριπλασιάζει το όριο κανενός και ένα θορυβώδες μέλος δεν μπορεί να στερήσει το όριο από τα υπόλοιπα.
  • Η αναίρεση είναι ανά εντολέα. Το ημερολόγιο ενεργειών κάθε πελάτη καταγράφει μόνο τις μεταβολές που εκτέλεσε ο δικός του εντολέας. Μπορείτε να αναιρέσετε με ⌘Z τη δική σας επεξεργασία εισιτηρίου· δεν μπορείτε να αναιρέσετε του συνεργάτη σας — μέχρι να τη δείτε, η ανθεκτική λωρίδα την έχει ήδη επιλύσει (δείτε παρακάτω).

Η συμμετοχή είναι το όριο πρόσβασης — όχι το κλειδί σύζευξης

Ενότητα με τίτλο «Η συμμετοχή είναι το όριο πρόσβασης — όχι το κλειδί σύζευξης»

Δύο ξεχωριστά διαπιστευτήρια απαντούν σε δύο ξεχωριστές ερωτήσεις. Το κλειδί σύζευξης (δείτε το Απομακρυσμένος έλεγχος και κινητό) πιστοποιεί μια συσκευή στον διακομιστή. Η συμμετοχή εξουσιοδοτεί τον χρήστη αυτής της συσκευής να ενεργεί μέσα σε έναν χώρο εργασίας. Μια συζευγμένη συσκευή της οποίας ο χρήστης δεν έχει συμμετοχή σε έναν χώρο εργασίας δεν μπορεί ούτε να τον διαβάσει ούτε να τον μεταβάλει.

Η συμμετοχή είναι μία γραμμή ανά (χώρο εργασίας, χρήστη), αποθηκευμένη στο δικό του αρχείο βάσης δεδομένων κάθε χώρου εργασίας — οπότε το «ποιος βρίσκεται σε αυτόν τον χώρο εργασίας;» είναι μια συνηθισμένη ανάγνωση περιορισμένης εμβέλειας, ενώ η αντίστροφη ερώτηση, «σε ποιους χώρους εργασίας βρίσκομαι;», απλώνεται αναγκαστικά σε πολλά αρχεία και είναι μία από τις λίγες επιτρεπόμενες διανομές μεταξύ χώρων εργασίας. Είναι αυτό που εκτελεί ο επιλογέας χώρου εργασίας πριν επιλεγεί οποιοσδήποτε χώρος εργασίας.

Κάθε γραμμή φέρει έναν ρόλο από μια πενταβάθμια κλίμακα με σειρά ιεραρχίας:

Ρόλος Επιτρέπεται
owner Πλήρης έλεγχος, συμπεριλαμβανομένης της διαγραφής του χώρου εργασίας· ακριβώς ένας ανά χώρο εργασίας
admin Διαχείριση μελών, προσκλήσεων, ρυθμίσεων, καταστροφικών λειτουργιών
member Καθημερινή ανάγνωση-εγγραφή: μηνύματα, εισιτήρια, εκτελέσεις πρακτόρων
viewer Μόνο ανάγνωση σε όλο τον χώρο εργασίας· τα globs εξαίρεσης μυστικών κρύβουν τις αντίστοιχες διαδρομές σε επιφάνειες που φέρουν κώδικα
guest Ίδιο κατώτατο όριο μόνο-ανάγνωσης με τον θεατή, στον χαμηλότερο βαθμό της κλίμακας

Κάθε απόφαση πρόσβασης ανάγεται στο «είναι ο ρόλος του καλούντος τουλάχιστον X». Το κατώτατο όριο προκύπτει από το είδος της λειτουργίας, εκτός αν αυτή δηλώνει κάποιο ρητά: μια ανάγνωση απαιτεί guest, μια μεταβολή απαιτεί member, μια καταστροφική λειτουργία απαιτεί admin. Τα ACL ανά αντικείμενο πέρα από τις παραχωρήσεις ανά αποθετήριο παρακάτω μένουν σκόπιμα εκτός πεδίου.

Οι προσκλήσεις είναι ο τρόπος με τον οποίο απονέμεται η συμμετοχή, από τις Ρυθμίσεις → Χώρος εργασίας → Μέλη. Ένας διαχειριστής εκδίδει έναν κωδικό μίας χρήσης (ως σύνδεσμο ή QR) που λήγει μετά από επτά ημέρες προεπιλεγμένα· ο διακομιστής αποθηκεύει μόνο το hash SHA-256 του κωδικού και ο ίδιος ο κωδικός εμφανίζεται ακριβώς μία φορά. Η εξαργύρωσή του δημιουργεί τον χρήστη επιτόπου (JIT), καταγράφει τη συμμετοχή στον ρόλο της πρόσκλησης (ποτέ owner — η κυριότητα μεταβιβάζεται ρητά, δεν απονέμεται ποτέ με πρόσκληση) και παραδίδει τη σκυτάλη στη σύζευξη συσκευής. Τα διαπιστευτήρια συσκευής που εκδίδονται μετά τη σύζευξη έχουν το ίδιο χρονικό όριο των 30 ημερών, οπότε μια σύζευξη δεν είναι μόνιμο κλειδί.

Οι παραχωρήσεις ανά αποθετήριο βρίσκονται κάτω από τη συμμετοχή. Επειδή ο διακομιστής κρατά πλήρη checkouts, η συμμετοχή στον χώρο εργασίας από μόνη της δεν πρέπει ποτέ να υπερέχει σε δικαιώματα από το forge: κάθε επιφάνεια που φέρει κώδικα — αρχεία, diffs, κριτική PR, αναζήτηση κώδικα — ελέγχει την παραχώρηση του μέλους σε αυτό το αποθετήριο. Οι παραχωρήσεις είναι ανά (χώρο εργασίας, χρήστη, αποθετήριο) και κατατάσσονται none → read → review → write, όπου write σημαίνει να κατευθύνεις εκτελέσεις πρακτόρων που γράφουν στο αποθετήριο. Η απουσία γραμμής σημαίνει none· οι κάτοχοι και οι διαχειριστές έχουν σιωπηρά write σε κάθε συνδεδεμένο αποθετήριο και δεν φέρουν καθόλου γραμμές.

Το Control Center χωρίζει την κοινόχρηστη κατάσταση σε δύο λωρίδες με αντίθετους κανόνες ανθεκτικότητας, γιατί τα δύο είδη κατάστασης αποτυγχάνουν με αντίθετους τρόπους.

Η παρουσία — ποιος είναι σε σύνδεση, πού βρίσκεται, τι πληκτρολογεί, τι κάνει ένας πράκτορας αυτή τη στιγμή — είναι άχρηστη τη στιγμή που θα απαρχαιωθεί. Αν αποθηκευόταν, κάθε επανασύνδεση θα έπρεπε να συμβιβάσει το «η βάση δεδομένων λέει ότι ο Σαμ επεξεργάζεται αυτό το εισιτήριο» με την πραγματικότητα. Γι’ αυτό η παρουσία δεν γράφεται ποτέ στη βάση δεδομένων. Ζει σε έναν κόμβο παρουσίας στη μνήμη του διακομιστή: οι πελάτες δημοσιεύουν με presence.update, οι ομότιμοι εγγράφονται με presence.watch και οι καταχωρίσεις λήγουν αυτόματα μετά από τρεις χαμένους παλμούς (οι παλμοί στέλνονται κάθε 10 δευτερόλεπτα· ο κόμβος καθαρίζει κάθε 5). Δεν υπάρχει τίποτα να συμβιβαστεί — μόνο να αφεθεί να λήξει.

Δύο ιδιότητες κρατούν τη λωρίδα αξιόπιστη:

  • Η ταυτότητα προέρχεται από τη συνεδρία, ποτέ από ορίσματα του πελάτη. Ο διακομιστής αντιστοιχίζει τη διαπιστευμένη συσκευή της σύνδεσης σε ένα αναγνωριστικό χρήστη· ένας πελάτης μπορεί μόνο να ενημερώνει τη δική του παρουσία, οπότε κανείς δεν μπορεί να πλαστογραφήσει την κατάσταση ή τη θέση άλλου συμμετέχοντα. Μια ρητή ενημέρωση offline (αποσύνδεση ή ο διακόπτης αορατότητας) αφαιρεί την καταχώριση αμέσως.
  • Ο κατάλογος περνά από κόμβο του διακομιστή. Οι ομότιμοι δεν ανταλλάσσουν ποτέ παρουσία απευθείας, οπότε κάθε συμμετέχων βλέπει τον ίδιο κατάλογο ανεξάρτητα από την τοπολογία. Οι πράκτορες δεν έχουν πελάτη, οπότε ο διακομιστής συνθέτει τις καταχωρίσεις τους από τη ζωντανή κατάσταση των εκτελέσεων — σκέψη, εκτέλεση, μπλοκάρισμα σε πύλη έγκρισης, ολοκλήρωση — με μία αντιπροσωπευτική εκτέλεση να καθορίζει την κατάσταση και τη θέση ενός πράκτορα, ώστε οι ταυτόχρονες εκτελέσεις να μην κάνουν τον κατάλογο να πηγαινοέρχεται.

Η διανομή είναι κλιμακωτή ανά καταναλωτή: το desktop και το web κινούνται σε πλήρη βαθμίδα (το πολύ μία εκπομπή καταλόγου ανά 100 ms), το τηλέφωνο σε βαθμίδα σύνοψης (ανά 500 ms), ώστε οι ενημερώσεις με ρυθμό κέρσορα να μην λιώσουν την PWA.

Η θέση ενός συμμετέχοντα — πού βρίσκεται αυτή τη στιγμή, μέχρι και το τεκμήριο — είναι μια σφραγισμένη ένωση: Space, File (αποθετήριο + διαδρομή

  • προαιρετική γραμμή), Pr, Ticket, PlanNode. Δεν έχει κάθε θέση πού να σε πάει: δεν υπάρχει διαδρομή προβολής αρχείου, οπότε μια θέση File αποδίδεται ως ετικέτα και όχι ως προορισμός. Η λωρίδα μεταφέρει επίσης ήπιες διεκδικήσεις («Ο Σαμ επεξεργάζεται αυτό το εισιτήριο», «ένας πράκτορας κατέχει αυτό το worktree»): ορατότητα σύγκρουσης, όχι κλειδώματα. Οι άγνωστοι τύποι θέσης αναλύονται σε null, οπότε ένας παλιός πελάτης απλώς δεν αποδίδει ό,τι δεν καταλαβαίνει.

Οι εγγραφές που πρέπει να επιβιώσουν — εισιτήρια, το κοινόχρηστο έγγραφο Σημειώσεων του χώρου, μηνύματα — ακολουθούν τον αντίθετο δρόμο: αυθεντικές, διατεταγμένες και αποθηκευμένες. Οι ταυτόχρονες επεξεργασίες επιλύονται με νίκη του τελευταίου συγγραφέα κατά τη σειρά παραλαβής του διακομιστή, ποτέ με τα ρολόγια των πελατών. Η επίλυση είναι σκόπιμα λεπτομερής: οι επεξεργασίες εισιτηρίων γίνονται ανά στήλη, οπότε δύο άτομα που επεξεργάζονται διαφορετικά πεδία του ίδιου εισιτηρίου καταγράφονται και τα δύο και κανένα δεν πατάει το άλλο (οι μεταβάσεις ροών εργασίας διατηρούν αντ’ αυτού αισιόδοξο κλείδωμα)· το έγγραφο Σημειώσεων του χώρου είναι μία στήλη, οπότε ολόκληρο το έγγραφο είναι LWW, με τις ήπιες διεκδικήσεις στη λωρίδα παρουσίας να κάνουν την ταυτόχρονη επεξεργασία ορατή αντί για κλειδωμένη. Δεν είναι σκόπιμα CRDT — το βάρος της απόδειξης πέφτει στο CRDT, και η LWW κατά σειρά παραλαβής μαζί με τις ορατές διεκδικήσεις καλύπτει τα πραγματικά μοτίβα συνεργασίας.

«Τελευταίος συγγραφέας» σημαίνει σειρά παραλαβής από τον διακομιστή, ποτέ ρολόι πελάτη — ένας φορητός υπολογιστής με λανθασμένη ώρα δεν μπορεί να κερδίσει μια επεξεργασία που έχασε. Πίσω από την ανθεκτική λωρίδα βρίσκεται η τροφοδοσία συγχρονισμού: τα triggers της SQLite προσθέτουν κάθε μεταβολή σε ένα αρχείο καταγραφής αλλαγών ανά χώρο εργασίας μέσα στη συναλλαγή εγγραφής, δεσμεύοντας έναν μονότονο αύξοντα αριθμό ανά χώρο εργασίας στην ίδια συναλλαγή με την ίδια τη μεταβολή. Μια υπηρεσία του διακομιστή παρακολουθεί αυτό το αρχείο καταγραφής και το μετατρέπει σε πλαίσια δέλτα με εκδόσεις, κατά σειρά αριθμού. Οι πελάτες εξελίσσονται από πλήρη snapshots σε δέλτα· ένα κενό στη σειρά γυρίζει αυτή την αποθήκη πίσω σε λειτουργία snapshot αντί να εφαρμόσει λανθασμένα πλαίσια.

Παρακολούθηση, καθοδήγηση, ανάληψη, επιστροφή

Ενότητα με τίτλο «Παρακολούθηση, καθοδήγηση, ανάληψη, επιστροφή»

Η λωρίδα παρουσίας δεν είναι απλώς ένας κατάλογος — τροφοδοτεί τα ρήματα της συνεργασίας:

  • Λειτουργία παρακολούθησης. Κάντε κλικ σε έναν συμμετέχοντα και ο πελάτης σας ακολουθεί τη θέση του, πλοηγούμενος όπως πλοηγείται εκείνος. Η παρακολούθηση ισχύει «μέχρι να ενεργήσετε»: τη στιγμή που θα πλοηγηθείτε με δική σας πρωτοβουλία, αποσυνδέεται. Ένα banner που μπορείτε να κλείσετε σάς ενημερώνει όταν ένας άλλος συμμετέχων παρουσιάζει έναν χώρο στον οποίο βρίσκεστε.
  • Καθοδήγηση. Ένα μήνυμα προς έναν πράκτορα που εκτελείται μπαίνει σε ουρά ως καθοδήγηση· ο πράκτορας το ξαναδιαβάζει πριν συνεχίσει στην επόμενη στροφή του, οπότε μπορείτε να διορθώσετε μια εκτέλεση εν κινήσει χωρίς να τη σκοτώσετε.
  • Ανάληψη. Η ανάληψη του ελέγχου μιας συνομιλίας παγώνει κάθε ενεργή εκτέλεση στο επόμενο καθαρό όριο στροφής της (μια εκτέλεση του Claude Code, που δεν έχει ασφαλές όριο, σταματά) πίσω από ένα snapshot git ανά στροφή, ώστε η ανάληψη να είναι αναστρέψιμη. Ένας ανθεκτικός δείκτης μπλοκάρει νέες αποστολές στον χώρο και επιβιώνει από επανεκκίνηση του διακομιστή — ο διακομιστής δεν συνεχίζει ποτέ αυτόματα μέσα σε μια μισοτελειωμένη επεξεργασία ανθρώπου. Ένας ζωντανός συντάκτης ανά worktree είναι ήπια διεκδίκηση: μια δεύτερη ανάληψη είναι ορατή και απορρίπτεται, δεν συγχωνεύεται σιωπηλά.
  • Επιστροφή. Η επιστροφή του ελέγχου δημοσιεύει στον χώρο μια δομημένη σύνοψη diff του worktree, τη βάζει σε ουρά ως καθοδήγηση για τις παγωμένες εκτελέσεις, τις συνεχίζει και καθαρίζει τον δείκτη.

Η καθοδήγηση και η ανάληψη είναι εκ των υστέρων παρεμβάσεις. Ο δείκτης αυτονομίας είναι η μόνιμη ρύθμιση: ανά χώρο, ανά πράκτορα, ορίζεται από την κεφαλίδα του χώρου και αποφασίζει τι συμβαίνει όταν ένας πράκτορας φτάνει σε μια ενέργεια πίσω από πύλη.

Επίπεδο Τιμή μετάδοσης Επίδραση σε κλήση εργαλείου πίσω από πύλη
Μόνο πρόταση proposeOnly Απορρίπτεται ευθέως, με αιτιολογία που ο πράκτορας καλείται να μεταφέρει — μπορεί να περιγράψει την ενέργεια αλλά όχι να την εκτελέσει
Ενέργεια με έγκριση actWithApproval Η πύλη έγκρισης με αποτυχία-κλεισίματος: μία επιβεβαίωση εμφανίζεται σε συνδεδεμένο χειριστή και χωρίς εγκρίνοντα η απάντηση είναι απόρριψη
Ελεύθερη ενέργεια actFreely Προεγκεκριμένη, εκτός από τις περιπτώσεις όπου εφαρμόζεται σκληρός κανόνας deny των περιορισμών — ένας κανόνας deny κερδίζει πάντα
(χωρίς τιμή) null Ίδιο με την ενέργεια με έγκριση: η προεπιλογή του διακομιστή

Δύο πράγματα βρίσκονται έξω από τον δείκτη και προς τις δύο κατευθύνσεις. Το ίδιο το ρήμα εξόδου μιας λειτουργίας είναι καρφιτσωμένο και δεν μπορεί ποτέ να απορριφθεί, ό,τι κι αν λέει ο δείκτης — η λειτουργία ενορχήστρωσης ήταν κάποτε δομικά ανίκανη να εκπέμψει το μοναδικό παραδοτέο της, επειδή ένα προκαθορισμένο λειτουργίας απέρριπτε την κλάση εφέ που δηλώνει αυτό το ρήμα. Και ένα σκληρό deny από τους περιορισμούς ενεργειών δεν μπορεί να παρακαμφθεί από το actFreely.

Μηδενική παλινδρόμηση σε ατομική χρήση

Ενότητα με τίτλο «Μηδενική παλινδρόμηση σε ατομική χρήση»

Όλα αυτά είναι προσθετικά. Η ροή εργασίας ενός μεμονωμένου χειριστή παραμένει αμετάβλητη: με έναν άνθρωπο και χωρίς πράκτορες ο κόμβος παρουσίας δεν έχει τίποτα να μεταδώσει, οπότε η λωρίδα εκπέμπει το αρχικό snapshot της και μετά αδρανεί με μηδενική κίνηση· ο μηχανισμός LWW και συγχρονισμού είναι η ίδια διαδρομή ενός μόνο συγγραφέα που ήταν πάντα· και το κλειδί σύζευξης παραμένει το διαπιστευτήριο συσκευής που ήταν πάντα. Η συμμετοχή είναι ήδη το όριο πρόσβασης — με έναν άνθρωπο υπάρχει μία μόνο γραμμή κατόχου και κανένα στοιχείο διεπαφής καταλόγου.