Zum Inhalt springen
im BauBuild-Phasen 0–8 fertig · Phase 9 (Orchestrierung der Pipeline) gebaut bis zu Browser-Treiber und Cron-Sweeper · als Nächstes: Rate Limiting (Phase 10), dann das Deployment

MeetMind: Meeting-Videos zu belegten Notizen, das Video bleibt im Browser

Nuxt-4-App für Transkripte mit Zeitstempeln, belegte Zusammenfassungen und Q&A aus Meeting-Videos; ffmpeg.wasm extrahiert das Audio im Browser. Nicht deployt; das Modell lief einmal echt, mit einem Testmeeting.

Ein einstündiges 720p-Meeting-Video ist etwa 1 GB groß, und wer es hochlädt, gibt die ganze Aufnahme aus der Hand. Notizen aus einem Meeting verdienen nur Vertrauen, wenn jede Entscheidung und Antwort auf tatsächlich gesagte Worte verweist.

Stack:
Nuxt 4 + Nitro · ffmpeg.wasm (single-thread, Web Worker) · Groq whisper-large-v3-turbo · Groq gpt-oss-120b (structured outputs) · Workers AI bge-m3 @ 1024 dims · Supabase Postgres (pgvector) · Cloudflare R2 (presigned PUT) · Nuxt UI v4 · zod 4 · Vitest + playwright-core
Zeitraum:
2026
veröffentlicht:
MeetMind: Architektur Drei Spalten: der Browser, der Nitro-Server und die externen Dienste. Im Browser geht eine Videodatei an einen Web Worker mit Single-Thread-ffmpeg.wasm, der Mono-Opus mit 16 kHz schreibt; das Video bleibt im Browser. Der Browser registriert das Meeting beim Server, der eine Presigned-PUT-URL erzeugt und jenseits der Speicherobergrenze mit 507 ablehnt; dann lädt der Browser nur das Audio direkt nach Cloudflare R2 hoch, das es nach 30 Tagen ablaufen lässt, und der Server prüft das gespeicherte Objekt per HEAD-Anfrage. Meeting-Zeilen, Transkripte, Zusammenfassungen und Vektoren liegen in Supabase Postgres mit pgvector, das jede Route liest und schreibt und in dem Row-Level Security keine Policies hat. Die Route zum Transkribieren schickt eine Anfrage pro Meeting an Groq Whisper large-v3-turbo. Die Routen zum Zusammenfassen und Fragen stellen das Zitat an den Anfang und prüfen jedes Zitat mit attestQuote gegen das Transkript; ihr Modell ist Groq gpt-oss-120b, das einmal echt lief, mit einem synthetischen Meeting. Die Routen zum Indexieren und Fragen nutzen Workers AI bge-m3 mit 1024 Dimensionen: Index bettet 45-Sekunden-Fenster ein, und Frage bettet die Frage ein. Whisper und Workers AI sind gegen die echten Dienste geprüft. Belegte Punkte und Antworten gehen an die Meeting-Seite, die den Player, das Transkript und das Zitat unter jedem Punkt zeigt. Cloudflare Workers ist unter dem Server gestrichelt: Nichts ist deployt, weil die 30,7 MiB große Wasm-Datei über der Asset-Grenze von 25 MiB liegt. Browser · das Video bleibt hier Nitro-Server · läuft lokal Externe Dienste Videodatei etwa 1 GB pro Stunde ffmpeg.wasm-Worker Single-Thread · WORKERFS 16 kHz Mono-Opus Meeting-Seite Player · Transkript Zitat unter jedem Punkt registrieren · abschließen Presigned PUT · HEAD-Prüfung 507 über der Obergrenze transkribieren eine Anfrage pro Meeting zusammenfassen · fragen Zitat zuerst Prüfung mit attestQuote indexieren · fragen 45-s-Fenster max. 5 pro Meeting Supabase Postgres pgvector RLS ohne Policies Cloudflare R2 nur Audio Ablauf nach 30 Tagen Groq Whisper large-v3-turbo per Vertragstest geprüft Groq gpt-oss-120b Zusammenfassungen · Antworten ein echter Testlauf Workers AI bge-m3 1024 Dimensionen per Vertragstest geprüft nur Audio, Presigned PUT Cloudflare Workers nicht deployt 30,7-MiB-Wasm, Grenze 25 MiB
Das Video bleibt im Browser, der nur das extrahierte Audio hochlädt, direkt nach R2. Nitro-Routen rufen die Modelle auf und prüfen jedes Zitat gegen das Transkript. Das Modell für Zusammenfassungen lief echt über ein synthetisches Meeting, und nichts ist deployt. · seitlich scrollen, um alles zu sehen

Problem und Rahmenbedingungen

Eine einstündige 720p-Aufnahme ist etwa 1 GB groß (ADR-0001). Sie hochzuladen ist langsam, kostet Speicher und übergibt einem Server das ganze Video, obwohl die Transkription nur die Sprache braucht. Der Free Tier von Whisper bei Groq begrenzt einen Upload auf 25 MB, und Whisper rechnet intern ohnehin auf 16 kHz Mono herunter; alles darüber ist verschwendet. Daraus folgt das eine Versprechen von MeetMind: Das Rohvideo verlässt nie den Browser.

Die zweite Anforderung ist Vertrauen in die Notizen. Eine Zusammenfassung, die eine Entscheidung nennt, oder eine Antwort auf „was haben wir zum Budget beschlossen?“ ist nur nützlich, wenn man sie an dem Moment prüfen kann, in dem es gesagt wurde. Deshalb muss jede Entscheidung, jede Aufgabe und jede Antwort ein Zitat tragen, und das Zitat muss im Transkript gefunden werden, bevor etwas gespeichert oder angezeigt wird.

Die Rahmenbedingungen waren Free Tiers und keine Zugangsdaten. Das Ziel sind 0 EUR/Monat. Das Token-Kontingent pro Minute bei Groq, 8.000 Tokens pro Minute für das Modell der Zusammenfassungen (ADR-0006), ist die bindende Grenze, nicht das Tageskontingent; lange Transkripte werden daher in aufeinanderfolgenden, getakteten Chunks zusammengefasst. Der Entwicklungsrechner und die CI hatten den größten Teil des Baus über keine Cloud-Keys, also sitzt jeder Cloud-Dienst hinter einer Schnittstelle mit einem lokalen Treiber oder einem Stub, und Entwicklung und CI laufen ohne Konten.

Architektur

MeetMind ist eine Nuxt-4-App mit Server-Routen in Nitro; die Modell-Keys liegen nur auf dem Server. Ein Video, das auf die Startseite gezogen wird, geht an einen Web Worker mit Single-Thread-ffmpeg.wasm, der die Datei per WORKERFS einhängt, statt sie in den WASM-Heap zu kopieren, und die erste Tonspur als Mono-Opus mit 16 kHz und 16 kbit/s schreibt. Das Video bleibt auf dem Rechner.

Der Upload hat drei Schritte. POST /api/meetings validiert einen kleinen JSON-Body, prüft die Speicherobergrenze und erzeugt eine 15 Minuten gültige Presigned-PUT-URL für Cloudflare R2. Der Browser lädt das Audio selbst nach R2 hoch. Ein Abschluss-Aufruf prüft das Objekt dann per HEAD und nimmt es nur an, wenn seine Größe der angegebenen Byte-Zahl entspricht und ein gemeldeter Medientyp audio/ogg ist; sonst wird das Objekt gelöscht und das Meeting als fehlgeschlagen markiert. Meeting-Metadaten, Transkripte, Zusammenfassungen und Vektoren liegen in Supabase Postgres mit pgvector.

Jede Verarbeitungsstufe ist eine Route, die ein Button auf der Fortschrittsseite des Meetings startet. Transcribe schickt das Audio in einer Anfrage pro Meeting an Groq whisper-large-v3-turbo und speichert Segmente mit ganzzahligen Millisekunden-Offsets, unter einem Compare-and-Swap-Claim mit Lease. Summarize teilt das Transkript an Segmentgrenzen in Chunks von höchstens 2.500 geschätzten Tokens, so bemessen, dass ein Chunk und seine Antwort in das Token-Kontingent einer Minute passen, schickt sie nacheinander an gpt-oss-120b, getaktet über ein Slot-Ledger in Postgres, das sich die Q&A-Aufrufe teilen, und verlangt strikte Structured Outputs, deren Schema das Zitat vor die Aussage stellt. reconcile() sucht dann jedes Zitat mit attestQuote im Transkript: Der Text wird normalisiert und in ganzen Tokens abgeglichen, sodass „approved by everyone“ nicht in „disapproved by everyone“ gefunden wird, und Schriften ohne Leerzeichen, etwa Japanisch oder Thai, werden Zeichen für Zeichen abgeglichen. Ein Punkt, dessen Zitat nicht gefunden wird, fällt weg, und der Zeitstempel kommt aus dem Segment, in dem das Zitat steht.

Index bettet Fenster von etwa 45 Sekunden mit Workers AI bge-m3 in 1024 Dimensionen ein. Ask bettet die Frage ein, ruft die vier nächsten Fenster ab und lässt gpt-oss-120b mit Zitaten antworten, die gegen genau diese abgerufenen Segmente bestehen müssen. Jedes Meeting ist nur über seinen eigenen, nicht erratbaren Link erreichbar, ohne Liste und ohne Suche über Meetings (ADR-0013). Ein natives Audio-Element spielt das Audio über eine Byte-Range-Route auf demselben Origin ab, und ein Klick auf ein Zitat springt an die zitierte Zeile. Die Entscheidungen dahinter stehen im Entscheidungsprotokoll weiter unten, jeweils mit der Alternative, gegen die sie sich durchgesetzt haben.

Was wirklich gelaufen ist

Die Unit-, End-to-End- und Browser-Tests laufen gegen Stubs und lokale Treiber. Der Stub für Zusammenfassungen zitiert das Transkript, statt Text zu erfinden, und sagt in seiner eigenen Ausgabe, dass er ein Platzhalter ist. Eine Canary-Option lässt ihn ein Zitat hinzufügen, das nicht im Transkript steht, sodass ein End-to-End-Test über HTTP belegt, dass die Prüfung es verwirft; ohne sie würde eine abgeschaltete Prüfung jeden Test ohne Zugangsdaten bestehen.

pnpm test:services prüft die echten Dienste. Am 23. Oktober 2026 bestanden 17 von 17 Tests gegen R2, Supabase, Groq Whisper und Gemini, festgehalten im Pull Request #14 des Repositorys. Die Whisper-Tests schicken echtes Ogg/Opus-Audio und lesen die Antwort mit dem Parser aus der Produktion. Die Gemini-Tests prüfen, dass 768 Dimensionen eingehalten werden, dass Vektoren mit Einheitslänge zurückkommen und dass eine Frage näher an der Passage liegt, die sie beantwortet. Die Embeddings sind seitdem von Gemini zu Workers AI gewechselt (ADR-0014). Alle neun Datenbank-Migrationen sind auf das echte Supabase-Projekt angewendet, und der R2-Bucket hat seine CORS- und Lifecycle-Regeln.

Kein automatisierter Test ruft das Chat-Modell auf. Am 25. Oktober 2026 lief ein synthetisches einstündiges Meeting über die echten Routen gegen das echte Groq-Konto, und zuerst ließ es sich gar nicht zusammenfassen: Das Modell verbrauchte sein voreingestelltes Ausgabekontingent mit Reasoning, jede Antwort brach also ab, und ein voller Chunk samt Antwort kam auf mehr als die 8.000 Tokens, die eine Minute erlaubt. Kleinere Chunks, eine feste Ausgabegrenze und niedriger Reasoning-Aufwand behoben das. Danach verwarf die Prüfung die meisten korrekt zitierten Punkte, weil Whisper Sätze auf mehrere Segmente verteilt; ein Zitat darf jetzt über bis zu drei aufeinanderfolgende Segmente reichen, und 72 von 73 Aufgaben bleiben erhalten. Das Meeting war synthetisch; geprüft ist also der Abgleich wörtlicher Zitate, nicht die Qualität von Zusammenfassungen eines echten Meetings.

GitHub Actions lief zuletzt am 22. September 2026 erfolgreich. Seit dem 22. Oktober 2026 scheitert jeder Lauf, bevor ein Schritt startet, wegen einer Abrechnungssperre am Konto, nicht wegen eines fehlschlagenden Tests. Die Gates laufen stattdessen lokal: pnpm ci:local führt die sechs Jobs des Workflows aus, wertet einen Job, den es nicht ausführen kann, als Fehler, und ein Paritätstest sorgt dafür, dass es mit der Workflow-Datei übereinstimmt. Der letzte vollständig grüne Lauf war am 23. Oktober 2026.

Ergebnisse

Die Build-Phasen 0 bis 8 der 15 im Plan (0 bis 14) sind gebaut: Extraktion im Browser, Upload, Transkription, der Transkript-Player, belegte Zusammenfassungen, und belegtes Q&A über ein Meeting; die Suche über Meetings aus Phase 8 wurde später entfernt (ADR-0013). Mehrere Vorgaben des Plans änderten sich nach Prüfung: das Modell für Zusammenfassungen, das Embedding-Modell, ein Waveform-Player, zurückgestellt zugunsten des nativen, und die Transkription in Chunks, entfallen, weil die App keine Datei über der Grenze von Groq erzeugen kann. Jede Änderung hat ihre Begründung in einem ADR.

Die Extraktion ist der gemessene Teil. Am 8. Juli 2026 wurde eine einstündige 720p-Fixture mit 1.045,8 MB in 16,0, 16,5 und 16,0 Sekunden über drei Läufe auf einer Seite zu 8,11 MB Opus, ohne Out-of-Memory-Fehler, in Headless Chrome auf einem MacBook Air M5. Die Fixture ist ein Testbild mit einem Sinuston, kein Meeting: Ein Sinuston lässt sich schneller kodieren als Sprache, die Größenordnung ist also der Befund, und der erneute Lauf mit einer echten Aufnahme ist offen. Safari und Firefox sind ungetestet.

Nichts ist deployt. Ein Mess-Spike vor der Orchestrierungsphase (ADR-0009, 22. Oktober 2026) baute den Server für Cloudflare Workers: Das Bundle hat 1,69 MB, 495 kB mit gzip, und lädt unter workerd mit nodejs_compat. Der Blocker ist ffmpeg-core.wasm, 30,7 MiB gegenüber der Grenze von 25 MiB pro Asset, die wrangler schon lokal durchsetzt und die in jedem Plan gleich ist. Die Optionen sind, die Datei aus R2 zu laden, sie mit gzip in 9,7 MiB zu verschicken, einen kleineren Core zu bauen oder auf WebCodecs zu wechseln; gewählt ist keine. Der Spike korrigierte auch ein früheres Audit: Die lokalen Treiber scheitern unter workerd mit einem 500, ein Deployment ohne Zugangsdaten schlägt also sichtbar fehl, statt Stub-Transkripte auszuliefern.

Phase 9, die Orchestrierung, ist bis zu Browser-Treiber und Cron-Sweeper gebaut (ADR-0012). Im Free-Plan von Workers braucht ein einstündiges Meeting etwa 8 Minuten getakteter Groq-Aufrufe; jede Anfrage erledigt daher einen begrenzten Schritt: Die Fortschrittsseite führt ein Meeting durch alle Stufen, solange ein Tab sie offen hat, und ein deployter Worker würde bei geschlossenem Tab weitermachen, höchstens einen Schritt alle 5 Minuten. Das lief bisher nur unter lokalem workerd. Als Nächstes kommt Phase 10, Rate Limiting. Eine DELETE-Route nur für Admins schloss die Lücke, die das Repository zuerst genannt hatte (ADR-0011), und Authentifizierung hat noch keine Phase.

Lehren

Eine Prüfung ist nur so gut wie ihre benannten Grenzen. attestQuote belegt, dass Worte gesagt wurden; ein weggelassenes „nicht“ sieht es nicht. Das neben die Prüfung zu schreiben und das Zitat unter jedem Punkt zu zeigen, damit Lesende selbst urteilen, war mehr wert als eine weitere Regel im Abgleich.

Ein Review braucht einen Diff, den es lesen kann. Die Datei, in der Modellausgabe zu gespeicherten Daten wird, enthielt ein echtes NUL-Byte als Token-Trenner; Git behandelte sie deshalb als binär, und fünf Review-Runden gingen über sie hinweg, ohne eine Zeile zu sehen. Der Trenner ist jetzt ein Leerzeichen, und ein Test scheitert an jedem Steuerzeichen in einer versionierten Textdatei.

Bestandene Tests können mehr behaupten, als sie belegen. Ein manuelles Review mit vier adversarialen Durchgängen fand sechs Mutationen, die die ganze Suite durchließ, darunter das Weglassen der hinteren Token-Grenze und das Verschieben der Mindestlänge für Zitate an eine beliebige Stelle zwischen 11 und 24. Jede Korrektur wurde belegt, indem die Mutation, die überlebt hatte, erneut lief.

Erst die Laufzeitumgebung messen, dann für sie entwerfen. Das schlimmste Betriebsrisiko des Audits gab es auf Workers gar nicht, und der echte Blocker war eine Dateigröße, die wrangler dev lokal meldet. Die Subrequests pro Route wurden später gemessen, am 25. Oktober 2026; die CPU-Zeit noch nicht, also geht der Entwurf der Orchestrierung vom ungünstigsten Fall aus und macht die Größe jedes Schritts zu einer Einstellung (ADR-0012).

Zentrale Entscheidungen

  1. 01 Audio-Extraktion im Browser mit Single-Thread-ffmpeg.wasm

    ffmpeg.wasm mit dem Single-Thread-Core läuft in einem Web Worker, liest das Video über einen WORKERFS-Mount, startet pro Auftrag eine frische Instanz und kodiert die erste Tonspur als Mono-Opus mit 16 kHz und 16 kbit/s. Hochgeladen wird nur dieses Audio.

    Extraktion auf dem Server heißt, das Video hochzuladen; das bricht das eine Versprechen des Produkts und sein Kostenmodell auf Free Tiers. Der Multi-Thread-Core braucht Cross-Origin-Isolation, durch die Einbettungen von Drittanbietern nicht mehr funktionieren, und gilt upstream als instabil. Eine Stunde FLAC sind 55–75 MB, über der Grenze von 25 MB bei Groq. mediabunny blieb der Ausweichweg, falls ffmpeg.wasm an Eingaben von 1 GB scheitern sollte; der Spike zeigte, dass es das nicht tut.

    ADR-0001 und ADR-0002. WORKERFS hält das Video aus dem WASM-Heap heraus, dessen Obergrenze 2 GB beträgt, und eine frische Instanz pro Auftrag hält den Speicherverbrauch konstant, weil der Heap nie schrumpft. Derselbe Core ist die Datei, die das Deployment blockiert, wie oben beschrieben.

    verworfen: Extraktion auf dem Server aus einem hochgeladenen Video · Der Multi-Thread-Core (core-mt) · Verlustfreies FLAC oder WAV hochladen · mediabunny auf WebCodecs

  2. 02 Direkter Upload vom Browser nach R2, Prüfung danach auf dem Server

    POST /api/meetings erzeugt mit aws4fetch eine 15 Minuten gültige Presigned-PUT-URL, der Browser lädt das Audio direkt nach R2 hoch, und ein Abschluss-Aufruf prüft das Objekt per HEAD: Die Größe muss der angegebenen Byte-Zahl entsprechen, und ein gemeldeter Medientyp muss audio/ogg sein, sonst wird das Objekt gelöscht und das Meeting als fehlgeschlagen markiert.

    Supabase Storage erlaubt 1 GB insgesamt und 50 MB pro Datei mit bezahltem Egress, R2 dagegen 10 GB und kostenlosen Egress. Ein Proxy verdoppelt die Bandbreite und stellt den Server in den Datenpfad. Eine signierte Länge kann nur einen exakten Wert festlegen, nie ein Maximum, und wie R2 sie prüft, ist nicht dokumentiert. aws4fetch ist ein abhängigkeitsfreier Client mit 10 KB, der unverändert auf Node und Workers läuft.

    ADR-0003. Die Prüfung sieht nicht in die Bytes: Ein Client, der ein Video als audio/ogg mit genau der angegebenen Länge auszeichnet, käme durch. Damit gäbe nur die hochladende Person ihre eigene Datei preis, und der ADR nennt Transkodieren als einzige echte Abwehr.

    verworfen: Supabase Storage für das Audio · Uploads über den Nitro-Server leiten · content-length in die URL signieren · @aws-sdk/client-s3

  3. 03 Zitat zuerst, jedes Zitat gegen das Transkript geprüft

    Zusammenfassungen sind für Groq gpt-oss-120b mit strikten Structured Outputs gebaut. Das Schema verlangt das Zitat vor der Aussage, und reconcile() behält einen Punkt nur, wenn attestQuote sein Zitat im Transkript findet; der Zeitstempel kommt aus dem Segment, in dem das Zitat gefunden wurde, nie vom Modell.

    Llama 3.3 hat bei Groq keine Structured Outputs, und das Schema ist der ganze Mechanismus: Bei Constrained Decoding kommen die Schlüssel in Schema-Reihenfolge, also steht der Beleg vor der Aussage. Selbst gemeldete Konfidenz ist nicht kalibriert, und Lesende würden ihr zu sehr trauen. Ein Judge würde das Token-Kontingent pro Minute verbrauchen, um das herum der Entwurf gebaut ist, und er läuft in der CI nicht reproduzierbar.

    ADR-0006. Die Prüfung belegt, dass die zitierten Worte gesagt wurden, nicht, dass die daraus gezogene Aussage stimmt: Ein Zitat kann über eine Satzgrenze hinweg zusammengesetzt sein oder eine vorangestellte Verneinung weglassen und trotzdem bestehen, daher steht das Zitat unter jedem Punkt. Gegen das echte Modell lief es erstmals am 25. Oktober 2026, über ein synthetisches Meeting (Zahlen oben).

    verworfen: llama-3.3-70b-versatile, das ursprünglich geplante Modell · Ein selbst gemeldeter Konfidenzwert pro Punkt · Ein LLM-Judge über jede Zusammenfassung

  4. 04 Q&A über 45-Sekunden-Fenster, Zitate gegen das Abgerufene geprüft

    Cloudflare Workers AI bge-m3 mit 1024 Dimensionen bettet Fenster aus aufeinanderfolgenden Segmenten ein (etwa 45 Sekunden oder 1.500 Tokens, mit einem Segment Überlappung). Die vier nächsten Fenster gehen an gpt-oss-120b, und jedes Zitat muss nur gegen die abgerufenen Segmente bestehen; bleibt keines übrig, sagt die Antwort, dass nichts aus dem Meeting Abgerufene eine Antwort stützt.

    Ein Segment sind wenige Sekunden Sprache: „ja, machen wir das“ allein eingebettet findet nichts, weil das, dem es zustimmt, im Segment davor steht. Themengrenzen würden Kontingent für einen Gewinn verbrauchen, den niemand gemessen hat. MeetMind transkribiert jede gesprochene Sprache, und in einem kleinen Test mit erfundenem Text setzte bge-m3 die richtige Passage bei 8 von 8 deutschen und 7 von 8 englischen Fragen an die erste Stelle. Ein Zitat, das dem Modell nie vorlag, kann nicht das sein, was es gelesen hat. Ein Meeting hat so wenige Fenster, dass ein exakter Scan genügt, während HNSW mit einem selektiven Filter weniger Zeilen liefern kann als angefragt.

    ADR-0007, das Modell aus ADR-0014. Der Index speichert das Modell, die Dimensionen und einen Hash des Texts jedes Fensters; ein erneuter Lauf setzt daher fort, und eine Frage gegen einen veralteten Index wird abgelehnt statt beantwortet.

    verworfen: Jedes Segment einzeln einbetten · Themen-Chunking mit einem Modellaufruf · gemini-embedding-2, das Modell bis zum 2. November 2026 · Ein HNSW-Index von Anfang an

  5. 05 Passagen statt einer geschriebenen Antwort bei der Suche über Meetings

    superseded

    Die Suche liefert passende Fenster, höchstens 5 pro Meeting aus höchstens 20 Meetings, gerankt nach dem besten Treffer jedes Meetings über einen LATERAL-Join, und jedes Ergebnis springt im Player an den Anfang seines Fensters. Kein Modell schreibt eine Antwort über mehrere Meetings.

    Eine Antwort über mehrere Meetings braucht an jedem Zitat ein Meeting und pro Meeting eine erneute Prüfung auf Veraltung, und jedes neue Teil ist eine neue Stelle, an der die Integrität der Zitate scheitern kann. Ein flaches Top-K lässt ein langes Meeting alle anderen verdrängen. Die Fensterfunktion rankt die ganze gefilterte Menge, bevor sie etwas verwerfen kann. Es gibt keine gelabelten Daten, um einen Schwellwert abzustimmen, also liegt die Untergrenze bei 0.

    ADR-0008, am 2. November 2026 durch ADR-0013 abgelöst: Die Suche über Meetings wurde entfernt, und jedes Meeting ist nur noch über seinen eigenen, nicht erratbaren Link erreichbar.

    verworfen: Eine zusammengefasste Antwort, die mehrere Meetings zitiert · Ein flaches Top-K über alle Fenster · ROW_NUMBER() OVER (PARTITION BY meeting_id) · Ein abgestimmter Ähnlichkeitsschwellwert

  6. 06 R2-Speicherkosten begrenzt durch Lifecycle-Regel und Obergrenze

    Eine Bucket-Regel lässt Meeting-Audio nach 30 Tagen ablaufen, und POST /api/meetings antwortet mit 507, wenn die gespeicherten Bytes plus der neue Upload eine Obergrenze überschreiten würden, standardmäßig 8 GiB. Die Summe wird in SQL aus der Meetings-Tabelle abgeleitet, und eine offene Registrierung reserviert ihre Bytes, solange ihre Upload-URL gültig ist.

    R2 hat keine Ausgabengrenze: Jenseits des Free Tiers rechnet es ab, statt abzulehnen. Ein Zähler driftet, weil die Lifecycle-Regel Objekte löscht, ohne der Datenbank Bescheid zu geben. Jede Anfrage zu zählen kostet einen Datenbank-Round-Trip, mehr als die 0,36 US-Dollar pro Million Operationen, die es schützt. Eine Sperre blockiert Löschungen, auch die der Lifecycle-Regel, und die einzige Art von Abrechnungsalarm braucht einen Professional-Plan und führt R2 nicht auf.

    ADR-0010, 23. Oktober 2026. Die Obergrenze gilt global, nicht pro Aufrufer: Ohne Authentifizierung gibt es keinen Aufrufer, den man zählen könnte, also kann eine einzige missbräuchliche Person das Archiv für alle füllen. Seit dem 25. Oktober 2026 kann auch eine DELETE-Route nur für Admins, hinter einem Token auf dem Server, Speicher freigeben (ADR-0011).

    verworfen: Ein laufender Byte-Zähler · R2-Operationen pro Anfrage zählen · Eine Bucket-Sperre · Sich auf einen Abrechnungsalarm verlassen

Evaluationsergebnisse

Kennzahlen, gemessen im MeetMind-Repository
KennzahlWertStatus
1-h-720p-Video → Audio, im Browser16,0–16,5 sgemessen

Single-Thread-ffmpeg.wasm in einem Web Worker, kein Out-of-Memory-Fehler in drei Läufen auf einer Seite. Die Fixture ist synthetisch, ein Testbild mit einem 440-Hz-Sinuston, keine Sprache; ein Sinuston lässt sich schneller kodieren als Sprache, und ein erneuter Lauf mit einer echten Aufnahme steht noch aus.

Eingangsvideo → Audio, dieselben Läufe1.045,8 MB → 8,11 MBgemessen

16 kHz Mono-Opus, tatsächlich 18,9 kbit/s (ffprobe): nach der Rechnung des ADR etwa 8,5 MB pro Stunde, um den Faktor drei unter der Upload-Grenze von 25 MB bei Groq. Dieselbe synthetische Fixture (Testbild und 440-Hz-Sinuston), keine aufgenommene Besprechung.

Vom Zitatabgleich behaltene Aufgaben, echte Modellausgabe72/73gemessen

Das Modell zitierte jeden Punkt wörtlich. Bevor ein Zitat über aufeinanderfolgende Segmente reichen durfte, behielt die Prüfung nur 16 von 73, weil Whisper Sätze auf mehrere Segmente verteilt. Bei den Entscheidungen blieben 62, auf 46 verschiedenen Zeilen: Jeder Block des synthetischen Meetings wiederholt eine Formulierung. Ein synthetisches Meeting, kein aufgenommenes, und für Q&A ist noch keine Zahl festgehalten.

Faithfulness von Zusammenfassungen und Antwortenk. A.ausstehend

Es gibt keinen Eval-Datensatz; das Repository führt die Qualität der Extraktion als ungemessen (ADR-0006). Nötig sind echte Meeting-Transkripte, gelabelt mit ihren Entscheidungen, Aufgaben und Antworten, ausgewertet über echte Meetings.

Gemessen: MeetMind docs/adr/0002-ffmpeg-spike-results.md (8. Juli 2026, drei Läufe: 16,0 / 16,5 / 16,0 s; MacBook Air M5, Headless Chrome 150, Produktions-Build) · MeetMind docs/adr/0002-ffmpeg-spike-results.md (8. Juli 2026) · MeetMind docs/adr/0006-structured-summaries.md, Nachträge vom 25. Oktober 2026 (Groq gpt-oss-120b, ein synthetisches einstündiges Meeting über die echten Routen)

Kosten & Latenz

Kosten & Latenz
KennzahlWertStatus
Server-Bundle für Workers, gemeldet / gzip1,69 MB / 495 kBgemessen

Rund 3 % der Grenze von 64 MiB bei Workers. Ein lokaler Build unter workerd, kein Deployment.

ffmpeg-core.wasm / Asset-Grenze von Workers30,7 MiB / 25 MiBgemessen

wrangler dev weist die Datei schon lokal ab, vor jedem Upload, und die Grenze ist bei Free und Paid dieselbe. Das blockiert das Deployment.

CPU-Zeit pro Route auf Workersk. A.ausstehend

Die Subrequests wurden am 25. Oktober 2026 unter workerd gegen die echten Dienste gemessen: höchstens 19 pro Route (summarize), unter der Grenze von 50 im Free-Plan (ADR-0012). Die CPU-Zeit gegenüber den 10 ms des Free-Plans ist weiter ungemessen; sie wird beim Deployment aus den Workers Logs gelesen.

Monatliche Betriebskostenk. A.ausstehend

Ziel sind 0 EUR/Monat auf Free Tiers, und nichts ist deployt. R2 hat keine Ausgabengrenze, daher begrenzen eine Lifecycle-Regel von 30 Tagen und eine Obergrenze von 8 GiB den Speicher (ADR-0010). Ein Monat einer deployten Instanz liefert diesen Wert.

Gemessen: MeetMind docs/adr/0009-workers-measurement-spike.md §1 (22. Oktober 2026, Build mit NITRO_PRESET=cloudflare_module) · MeetMind docs/adr/0009-workers-measurement-spike.md §3 (22. Oktober 2026, wrangler dev)

Sicherheit & Fehlerfälle

Risiken

  • Keine Authentifizierung: Jede Route ist anonym, und der Server erreicht die Datenbank mit dem Service-Role-Key
  • Kein Rate Limiting: Wer einen Meeting-Link hat, kann mit Fragen Kontingent bei Groq und Workers AI verbrauchen; das Hochladen braucht einen Einladungscode (ADR-0015)
  • Ein Modell kann Worte zitieren, die wirklich gesagt wurden, und trotzdem falsch wiedergeben, was sie bedeuten
  • Ein Client kann unter seiner Upload-URL beliebige Bytes ablegen, auch ein als Audio ausgezeichnetes Video
  • R2 rechnet jenseits des Free Tiers ab, statt abzulehnen

Gegenmaßnahmen

  • Nichts ist deployt, und die CORS-Regel des R2-Buckets erlaubt nur http://localhost:3000. Row-Level Security ist aktiv, ohne Policies, also sehen die öffentlichen Keys nichts. Authentifizierung und Kontingente pro Person haben im Plan noch keine Phase
  • Rate Limiting ist Phase 10 des Plans und nicht gebaut. Bis dahin ist jede Anfrage begrenzt: 25 MB Audio (geprüft in zod, per CHECK in Postgres und beim HEAD im Abschluss), ein Slot-Ledger für Groq in Postgres, das sich alle Instanzen teilen, damit die Aufrufe für Zusammenfassungen und Q&A im Kontingent pro Minute bleiben, und höchstens 40 Chunks pro Zusammenfassung
  • Jedes Zitat muss gegen das Transkript bestehen, das Zitat steht unter jedem Punkt, und eine Antwort ohne verbliebenes Zitat sagt, dass nichts aus dem Meeting Abgerufene eine Antwort stützt
  • Der Abschluss löscht jedes Objekt, dessen Größe von der Angabe abweicht oder dessen gemeldeter Typ nicht audio/ogg ist; in die Bytes sieht er nicht, und ADR-0003 sagt das
  • Eine Lifecycle-Regel von 30 Tagen und eine Speicherobergrenze mit 507, abgeleitet aus der Datenbank (ADR-0010)