Systemarchitektur
Tessera Systemkarte
Stand 2026-08-12 · Quelle: Claude-Artefakt „Tessera Systemkarte“ — hier als Seite im Template, damit die Karte im Repo lebt und per Patch weiterwächst.
Der komplette selbst-besessene Stack — Identität, Code, Builds, Pakete, Runtime und Betrieb — mit den Flüssen dazwischen. Unten die nummerierten Lücken als Diskussionsgrundlage.
Weg einer Änderung
Vom lokalen Commit bis zur laufenden App — ohne GitHub, ohne Coolify, ohne fremde Registry. Der goldene Schritt ist der einzige manuelle: das bewusste Produktions-Gate.
flowchart LR
subgraph build [Build-Pfad]
dev[Entwickler<br/>git push rad] -->|push| seed[Radicle-Seed<br/>seed.tessera.at]
seed -->|rad sync| cib[CI-Broker<br/>baut + signiert]
cib -->|SSHSIG| zot[zot-Registry<br/>oci.tessera.at]
dev -.->|private Repos direkt, WireGuard 10.10.0.2| cib
end
subgraph deploy [Deploy-Pfad]
mosaikrepo[mosaik-Repo<br/>deployment.yaml] -->|zieht| flux[Flux<br/>pull, Minutentakt]
flux -->|reconcile| cluster[mosaik-Cluster<br/>App live]
end
zot ==>|Image-Tag von Hand nachziehen — das bewusste Prod-Gate| mosaikrepo
zot -.->|Image-Pull| clusterKyverno prüft die Signatur — ein fehlender Tag blockt die ganze Kustomization. Seed, Watcher, Broker und zot laufen alle auf der CI-Box — eine einzige Box trägt den ganzen Weg vom Push bis zum signierten Image.
Seit 2026-08-12 baut der Broker auch Patches: gleiche Tore, aber ein Patch-Build published nichts — kein Tag, keine Signatur (Details: README.md → „Die Tore“).
Weg eines Logins — und der Daten
Jede App spricht Standard-OIDC gegen die Bridge; nur die Bridge kennt atproto. Die Passkey-Ceremony läuft direkt zwischen PDS und Browser — kein Passwort, keine E-Mail existiert im System.
flowchart LR user[Nutzer<br/>Passkey am Gerät] -->|Login| app[App<br/>SDK oidc-rp] app -->|OIDC| bridge[OIDC-Bridge<br/>id.tessera.at] bridge -->|atproto OAuth| pds[PDS, atproto-Fork<br/>pds.tessera.at] pds ==>|WebAuthn-Ceremony — direkt im Browser, passwort- und mailfrei| user pds <-.->|did:plc| plc[plc.directory<br/>extern · Föderation] pds -.->|Jetstream| index[tessera-index<br/>Jetstream → SQLite] index -->|read-only HTTP| apps[alle Apps<br/>Identity, Flairs, …]
tessera-index ersetzt N × listRecords-Polling gegen den PDS. Für Handles ist zusätzlich je ein DNS-TXT-Record _atproto.<kürzel>.tessera.at nötig (aktuell manuell in der Hetzner-Zone). Die EVM-Wallet lebt im PDS und ist per Sign-Popup-Protokoll tessera-sign/1 für Apps nutzbar.
Wo alles läuft
Zwei eigene Standorte tragen den Stack — selfbot ist seit der Migration komplett draußen.
| Host | Rolle | Dienste |
|---|---|---|
| CI-Box (167.233.240.164) | Kritisch | Radicle-Seed (seed.tessera.at) · Explorer + Suche (code.tessera.at, Meilisearch) · radicle-watcher (Seeding ↔ PDS-Records) · CI-Broker (baut aus Radicle-Events) · zot-Registry (oci.tessera.at) · eigener CI-Radicle-Knoten (holt private Repos) |
| mosaik-Cluster (Talos · Flux · Kyverno · Envoy GW) | Herzstück | tessera-pds, -oidc, -web · tessera-index, -registry, -backup · cadence, ledger, skytess, start-complex · Tenants: salzgrotte, mbdrone, murmel, 2over1 · external-dns, cert-manager-hetzner, MetalLB, Gatus |
| hetzner-selfbot (ARM cax31, 46.225.72.168) | kein Tessera | Uptime Kuma — Kandidat für den Totmann-Check (G1) · Media-Stack, LiteLLM, ntfy · Coolify läuft noch, hostet nur Nicht-Tessera |
| extern (bewusst minimal) | Abhängigkeit | plc.directory (DID-Föderation) · Hetzner DNS + Object Storage (VolSync) · Let's Encrypt (HTTP-01/DNS-01) · Offsite-Ziel von tessera-backup |
Die Rolle der CI-Box ist wörtlich zu nehmen: sie ist der Single Point of Failure des Build-Pfads (G3).
Die sechs Schichten
Jede Schicht baut auf der darunter auf; die Identität trägt alles.
IIdentität — wer du bist
| Komponente | Domain | Wo | Was |
|---|---|---|---|
| atproto-Fork / PDS | pds.tessera.at | mosaik | Passkey-only Accounts, E-Mail-frei, föderiert via plc.directory; EVM-Wallet eingebaut. Fork privat (bis zahlende Nutzer). |
| tessera-oidc | id.tessera.at | mosaik | OIDC-Bridge: „Login mit Tessera“ für jede Standard-Auth-Lib. Admin-UI + Developer-Self-Service (/developers). |
| tessera-web | tessera.at | mosaik | Landing, Design-System-Katalog, shadcn-Registry unter /r. |
IICode-Hosting — was du erschaffst
| Komponente | Domain | Wo | Was |
|---|---|---|---|
| radicle-node | seed.tessera.at | CI-Box | Eigener Seed, Policy default:block — ein Tessera-Konto darf hosten, ohne dass jemand freischaltet. |
| radicle-explorer | code.tessera.at | CI-Box | Explorer mit Tessera-Skin + Volltextsuche (Meilisearch, radicle-search aus dem Explorer-Workspace). |
| radicle-watcher | — | CI-Box | Gleicht Seeding gegen PDS-Records ab; Besitz-Nachweis über Radicle-Delegates. Trägt zusätzlich das Gate (:8777, nur Stack-Netz, Shared Token): DID-gebundene Reads für Cadence — inkl. privater Repos, ohne deren Existenz zu leaken. |
IIIBuild & Supply Chain — vom Tag zum Image
| Komponente | Domain | Wo | Was |
|---|---|---|---|
| CI-Broker (cib) | — | CI-Box | Baut aus Radicle-Events (main-Push, Tag, Patches — Patches ohne Publish), signiert Images; Tag-Builds sind immutable Releases. |
| zot | oci.tessera.at | CI-Box | OCI-Registry für alle signierten Images; Kyverno im Cluster verweigert Unsigniertes. |
| CI-Radicle-Knoten | — | CI-Box | Holt private Repos direkt vom Entwickler-Node (persistierter connect via WireGuard). |
IVDistribution — Pakete & Bausteine
| Komponente | Domain | Wo | Was |
|---|---|---|---|
| tessera-registry | registry.tessera.at | mosaik | npm-Registry, in der Pakete Handles gehören (@alice.tessera.at/thing) — ohne Antrag, ohne Freischaltung. |
| tessera-sdk | @dev0zzz.tessera.at/sdk | Paket | Subpath-Exports: oidc-rp (Auth), identity (Profile, Wallet, Radicle, Zaps, Scores) — die eine Integrationsschicht. |
| shadcn-Registry | tessera.at/r | mosaik | @tessera-Items inkl. auth-oidc als kanonischem Auth-Baustein; asChild-frei per AST-Wächtertest. |
| tessera-cli | — | lokal | radicle init/create (Announce), pkg publish via SSHSIG — ohne Passkey-Ceremony publizierbar. |
VRuntime — wo Apps leben
| Komponente | Domain | Wo | Was |
|---|---|---|---|
| mosaik | *.tessera.at | Cluster | Talos/K8s, Flux (GitOps aus dem mosaik-Repo), Kyverno, Envoy Gateway, external-dns, cert-manager-hetzner, MetalLB, Gatus. Wildcard-Record → neue App ohne DNS-Flip. |
| Eigene Apps | cadence · ledger · skytess | mosaik | Plus start-complex als lebendes Template mit /kosten (Offene Bücher). |
| Tenants | 4 migrierte Apps | mosaik | salzgrotte, mbdrone, murmel, two-over-one — die ersten „fremden“ Nutzer der Plattform. |
VIFabrik & Betrieb — wie Neues entsteht & Bestehendes überlebt
| Komponente | Domain | Wo | Was |
|---|---|---|---|
| start-simple / -complex | start-complex.tessera.at | Template | Kanonische App-Vorlagen: Auth nur via SDK, Bun-Install/Node-Runtime, Pricing aus dem eigenen cgroup-Limit (P = ⌈C·φ⌉, Zykluskalender). |
| cadence | cadence.tessera.at | mosaik | Anwendungsfabrik und Code-Werkstatt: eigene Forge-Ansicht (Tree, Blobs, Commits, Diffs, Patches inkl. Reviews, Issues) über das Watcher-Gate — DID-gebunden, private Repos sichtbar. Dazu Runs, Registry-, zot- und Cluster-Sicht. Später Schreibwege (Scaffold, Roll-Button). Zukunft von code.tessera.at: G12. |
| tessera-index | — | mosaik | Netzwerkweiter Index aller at.tessera.*-Records (Jetstream → SQLite → read-only HTTP). |
| tessera-backup | — | mosaik | Nightly verschlüsselt offsite — deckt „Hetzner ist weg“, nicht nur „Volume kaputt“ (das macht VolSync stündlich). |
| tessera-agent | — | lokal | Claude-Memory verschlüsselt als Records ins private PDS-Repo — Keimzelle der Agent-Identität (A1). |
Lücken & Diskussionspunkte
Nummeriert, damit sie einzeln abarbeitbar sind. Reihenfolge = Priorisierung.
| # | Schwere | Lücke |
|---|---|---|
| G1 | Kritisch | Alerting endet im Nichts. Prometheus feuert, aber der matrix-receiver ist disabled; der externe Totmann-Check (Uptime Kuma → pds / id / cadence) ist offen. Ein nächtlicher PDS-Ausfall bliebe bis zum Morgen unbemerkt — und der PDS ist der Single Point of Identity für alles. |
| G2 | Kritisch | Bus-Faktor 1 — auch beim Wissen. Migrations-Pipeline, Kyverno-Falle, private-Repo-Sync: das Betriebswissen liegt in Claude-Memory, nicht in Doku, die ein Dritter (Mensch oder KI) findet. Direkter Anknüpfungspunkt für die MCP/Docs-Idee (G11). |
| G3 | Wichtig | Die CI-Box trägt alles außer der Runtime. Seed, Explorer, Watcher, Broker und zot auf einer einzigen Box: fällt sie, sind Code-Hosting, Builds und Image-Pulls gleichzeitig weg (laufende Pods überleben, Neustarts nicht). Seit der selfbot-Migration ist die Konzentration höher als je zuvor — bewusst schlank, aber unbewertet, was ein Ausfall konkret kostet. |
| G4 | Wichtig | M3 Multi-Device & Recovery fehlt. Ein verlorenes Gerät = verlorenes Konto (kein zweiter Passkey, kein kalter Rotation-Key). Vor zahlenden Nutzern zwingend — wer zahlt, erwartet Wiederherstellbarkeit. |
| G5 | Wichtig | M4 Payment fehlt. Prepaid in USDC (Hyperliquid) ist Vision; ohne sie bleiben Handle-Abo, Anti-Spam-Gate und Offene-Bücher-Abrechnung Theorie. Die Pricing-Mechanik in den Templates ist fertig — es fehlt der Zahlweg. |
| G6 | Reibung | Feedback-Schleife bis ~1 h. ARM-Build ~40 min plus bis zu 20 min Broker-Verzögerung zwischen Tag und Live. Optionen: Build-Cache, stärkere/x86-Build-Box, Broker-Polling verkürzen. |
| G7 | Reibung | Image-Bump von Hand. Als Prod-Gate gewollt, aber unergonomisch (YAML editieren + pushen). Der geplante Roll-Button in Cadence (erzeugt einen Radicle-Patch) behält das Gate und entfernt die Reibung. |
| G8 | Reibung | Handle-DNS manuell. Pro Handle ein TXT-Record von Hand in der Hetzner-Zone. Beim Signup automatisch anlegen (Hetzner-DNS-API) oder Wildcard + Traefik-Route — offene M4-Aufgabe. |
| G9 | Geplant | tessera create <app> fehlt (CLI Phase 4). Die Fabrik hat Templates, SDK und Registries — aber noch keinen Scaffold-Befehl, der sie zusammensetzt. |
| G10 | Klein | Hygiene-Reste. Meilisearch-Master-Key nur in der .env auf der Box (nicht in KeePass); OIDC-Client von start-complex noch unverifiziert (⚠ im Consent). |
| G11 | Idee | Agenten-Fläche (MCP). mcp.tessera.at mit „Login mit Tessera“-OAuth: read-only Tools (Identity, Code-Suche, Build-/Deploy-Status, Pakete) plus kuratierte Docs als Kern — gemeinsame Logik mit Cadence im SDK, nicht doppelt gebaut. Löst G2 gleich mit, wenn die Doku das erste Tool ist. |
| G12 | Geplant | Cadence ersetzt code.tessera.at. Die Werkstatt liest übers Watcher-Gate bereits alles inkl. privater Repos — zwei Vorbedingungen fürs Abschalten des Explorers: (a) login-freie Sicht auf öffentliche Repos in Cadence (Gate ohne DID liefert nur public), damit das öffentliche Schaufenster bleibt; (b) Volltextsuche als Gate-Route mit RID-Filter je DID — nie darf ein privater Treffer in einer anonymen Suche auftauchen. Es fällt nur das Explorer-Frontend: radicle-httpd bleibt (Watcher liest Delegates daraus), radicle-search + Meilisearch bleiben als Backend. Ein Container weniger auf der CI-Box, GPL-Skin-Pflege entfällt. |