Connections — API-Key-Lebenszyklus
Wie eine Personal-Brain-Instanz sich mit einer Business-Brain-Instanz verbindet und wieder trennt.
Für die architektonische Einordnung siehe ARCHITECTURE.md, für gelockte Entscheidungen ../DECISIONS.md (D24, D25).
1. Ablauf
Admin@Business User@Personal
(z.B. gustavo (z.B. gustavo
im Business-Admin-UI) auf seinem Mac)
│ │
│ 1. Invite generieren │
│ POST /admin/invites │
│ → { code: "KBL-7A3X", api_key: "sk_…" } │
│ (Code ist 24h gültig, one-shot) │
│ │
│ 2. Code an den Eingeladenen senden │
│ (E-Mail, Signal, o.Ä. — out of band) │
│ ────────────────────────────────────────────► │
│ │
│ │ 3. UI: "Business verbinden"
│ │ → URL + Code eingeben
│ │ POST /api/connect
│ │ { business_url, code }
│ ◄───────────────────────────────────────────── │
│ │
│ 4. Business validiert Code │
│ - existiert? │
│ - nicht abgelaufen? │
│ - noch nicht eingelöst? │
│ Bindet Code an callers Identity │
│ Deaktiviert Code (one-shot) │
│ │
│ 5. Response: { │
│ business_id: "kuble", │
│ business_name: "Kuble GmbH", │
│ api_key: "sk_…", │
│ scopes: [...] │
│ } │
│ ────────────────────────────────────────────► │
│ │
│ │ 6. Personal persistiert
│ │ in SQLite (api_keys Tabelle)
│ │ UI: "Verbunden mit Kuble ✓"
│ │
│ │ 7. UI: Brain-Liste laden
│ │ → GET /api/brain/list
│ │ Header: X-GTS-Key
│ ◄───────────────────────────────────────────── │
│ │
│ 8. UI: User haken ab, welche │
│ Projekte/Kunden syncen sollen │
│ │
│ ... ongoing sync (via /api/sync) ... │
│ │
│ n. Admin: "Trennen" │
│ DELETE /admin/connections/<id> │
│ → API-Key revoked │
│ │
│ n+1. Nächster Personal-Sync │
│ Response: 401 Unauthorized │
│ │ Personal UI:
│ │ "Verbindung getrennt durch Admin"
│ │ zeigt Kontakt-Hinweis
2. Zustände einer Connection
Aus Business-Sicht (Spalte status in connections-Tabelle):
| Status | Bedeutung |
|---|---|
invited |
Code ausgestellt, noch nicht eingelöst. Expires in 24h. |
active |
Eingelöst, funktioniert. |
revoked |
Admin hat widerrufen. Key funktioniert nicht mehr. Historisch sichtbar im Audit-Log. |
expired |
Code lief ab, ohne eingelöst zu werden. |
Aus Personal-Sicht analog: connecting, connected, disconnected, failed.
3. Scope (Phase 4+)
API-Keys haben einen Scope-Set. Im MVP der einzige Scope ist sync (Lesen + Schreiben von Brain-Sync-Daten). Erweiterbar:
sync.read— Nur-Lese-Personalsync.write— Bidirektional (Default)admin— Darf auf die Business-Seite zurückschreiben auch ohne Sync (z.B. ein Delegated-Admin-Setup)
Scopes werden in Phase 4 im Detail ausgearbeitet — MVP reicht sync.write.
4. Sicherheits-Constraints
- Code einmalig einlösbar — nach erstem Redeem deaktiviert, unabhängig vom Ergebnis.
- API-Key nie im Klartext loggen — nur das letzte Präfix (
sk_abc…). - Rate-Limit auf
/api/connect— 10 Versuche/IP/10min (verhindert Brute-Force auf Codes). - Key-Rotation: Admin kann einen Key ohne Disconnect rotieren (neuer Key ersetzt alten). Personal bekommt Push-Notification oder schlägt beim nächsten Sync fehl und muss den neuen Key eintragen.
- Offboarding: Bei "Mitarbeiter verlässt Firma" sollte der Admin die Connection revoken. Die historischen Brain-Daten auf der Personal-Instanz bleiben beim Ex-Mitarbeiter (kann nicht verhindert werden — git ist dezentral) — aber Business-Soul-Updates stoppen, und der Ex-Mitarbeiter kann nichts mehr ins Business schreiben.
5. Admin-Brain-Zugriff via Connection
Standardmässig gilt für category: admin Default-Deny: weder Web-User mit
Rolle member/viewer noch eingehende Sync-/MCP-Connections sehen das Brain.
Zugriff wird pro Brain und pro Connection explizit freigeschaltet:
Pro Connection (auf Business unter /admin/connections):
can_read_admin: 1— dieser Remote-Slug darf überhaupt Admin-Brains lesen. Default0. Toggle via UI odersetConnectionAdminAccess(db, id, true).
Pro Admin-Brain (Frontmatter):
category: admin
visibility: private # immer private auf admin-Brains
allowed_users: # Web-Accounts, die es trotz Rolle sehen dürfen
- olivia.schiffmann@kuble.com
allowed_connections: # Connection-Slugs, die es via Sync/MCP sehen dürfen
- olivia
Beide Hürden müssen genommen werden, damit ein Remote ein Admin-Brain pullen
oder über MCP anfragen kann. Der /api/sync/list + /api/sync/brain/[id]
Filter ruft canConnectionReadBrain() auf (siehe packages/server/src/lib/access.ts).
Admin-Brains ignorieren den visibility: team|public Filter — die ACL ist
der alleinige Gate.
6. Spiegel-Kontrolle (Pull-Seite)
Auf einer Personal-Instanz kann der Admin einzelne gemirrorte Brains vom Pull
ausschliessen. Gespeichert in mirror_exclusions(connection_id, remote_brain_id) —
eine lokale Präferenz, unabhängig vom Brain-Inhalt, überlebt Remote-Rename.
UI: /admin/connections/<id>/mirrors listet alle vom Remote gepullten Brains
plus ausgeschlossene (ohne lokale Kopie). Pro Zeile Pausieren / Wieder pullen.
Effekt des Pausierens:
- Der lokale Stand bleibt eingefroren — kein Update beim nächsten Pull.
- Das Brain kann weiterhin lokal gelesen werden; aus-Spiegel-entfernen (über die Detail-Seite) löscht zusätzlich die Datei.
Read-Only-Lock: Gemirrorte Brains sind auf der Personal-Seite nicht
editierbar (🔒 Spiegel von <remote>). Der doSave-Server-Action rejected
jeden Write, egal ob UI-Bypass oder nicht. Edits im Original drüben durchführen
— landen beim nächsten Pull hier.
7. CLI-Äquivalente
Was die Web-UI kann, kann auch die CLI:
# Business (Admin):
gts admin invite --email user@example.com # → Code + URL
gts admin connections list
gts admin connections revoke <id>
gts admin connections rotate-key <id> # neuen Key ausstellen, alten widerrufen
# Personal (User):
gts connect <business-url> <code>
gts connect list
gts connect revoke <business-slug>
gts sync [<business-slug>] # pull/push, logged in git + SQLite