Skip to content

feat(deploy): prepare the Vercel showcase with Supabase (#59) - #64

Merged
Fluory merged 11 commits into
mainfrom
claude/feat-vercel-supabase-59
Sep 24, 2026
Merged

Fluory merged 11 commits into
mainfrom
claude/feat-vercel-supabase-59

Conversation

@Fluory

@Fluory Fluory commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Warum

Fixes #59 · Auftrag des Orchestrators (2026-09-24): „Bereite Vercel Hosting vor aber mit Supabase statt Neon“. ADR-0001 D11 sah Vercel + Neon + R2 vor. Es werden keine Konten, Schlüssel, Cloud-Ressourcen oder Deploys angelegt – nur Code, Konfiguration und Anleitung.

Arbeitsstand

Was ist passiert (Klartext)

Das Projekt ist jetzt so vorbereitet, dass es auf Vercel laufen kann, mit Supabase für Datenbank und Dateiablage (statt Neon und Cloudflare R2 – ein Anbieter weniger). Auf Vercel gibt es keinen dauerhaft laufenden Hintergrund-Prozess; deshalb stößt die App die Verarbeitung direkt nach dem Hochladen und nach der Freigabe selbst an, zusätzlich einmal täglich per Zeitplan und über „Erneut verarbeiten“. Damit eine öffentliche Demo keine unkontrollierten KI-Kosten verursacht, darf jede Person nur eine begrenzte Zahl Anfragen pro Stunde hochladen. Ein Banner „Demo – nur synthetische Daten“ lässt sich einschalten. Eine Schritt-für-Schritt-Anleitung beschreibt, was du bei Supabase, Vercel und für den KI-Dienst anlegen musst. Lokal ändert sich nichts.

Plan-Pflicht (SYSTEM.md §4)

  • Kein Auslöser – keine Modulgrenze, öffentliche API, Migration, Auth/Rechte, kein Zahlungs-/Daten-/Infrapfad, höchstens zwei Module, keine Architekturvarianten, umkehrbar
  • Auslöser zutreffend – Impact Manifest ausgefüllt (Plan vor Code)

Impact Manifest

  • Betroffene Module: jobs/Worker-Verdrahtung, intake (Upload-Limit), requests (Zählung), app (Route, Actions, Layout), config, Doku/ADR.
  • Schnittstellen / Datenänderungen: neue interne Route GET|POST /api/jobs/drain (Bearer CRON_SECRET, 404 ohne Secret); POST /api/requests kann 429 antworten (nur mit UPLOAD_MAX_PER_HOUR); neue Variablen CRON_SECRET, JOB_DRAIN_INLINE, DEMO_MODE, UPLOAD_MAX_PER_HOUR. Keine Migration.
  • Akzeptanzkriterien: Prepare the Vercel showcase with Supabase (Postgres + Storage) instead of Neon + R2 #59.
  • Testplan: Unit (Route-Auth inkl. Schema-Groß/Kleinschreibung, Zeitbudget, Config inkl. Pflicht-Limit auf dem Showcase, Banner, Deployment-Budget), Integration (Drain-Route verarbeitet Jobs; gleichzeitige Drains exportieren genau einmal; Upload-Limit pro Person, vor dem Speichern).
  • Verifizierte Fakten: Pooler-Sicherheit (transaktionslokales set_config, unbenannte Prepared Statements, pg-boss ohne LISTEN, transaktionsgebundene Advisory-Locks); statement_timeout zusätzlich an der Rolle.
  • Offene Annahmen: –
  • Nicht-Ziele: siehe oben.
  • Risiken und Rollback: Features sind per Variable aus (Default); Rollback per Revert.

Geändert

  • src/app/api/jobs/drain/route.ts, src/app/_server/{drain,drain-request}.ts, src/job-drain.ts, src/worker.ts
  • src/app/api/requests/route.ts (429, after()), src/app/requests/actions.ts, src/app/requests/[id]/actions.ts (after())
  • src/features/intake/{submit,files,index}.ts, src/features/requests/{repository,index}.ts (Upload-Limit)
  • src/app/_components/demo-banner.tsx, src/app/layout.tsx, src/app/globals.css
  • src/config/env.ts, .env.example, vercel.json
  • docs/decisions/ADR-0001-pilot-architecture.md (Amendment D11), docs/technical/deployment-vercel.md, operations.md, architecture.md (Karte + Ausnahmenregister), scripts/supabase-bootstrap.sql, CHANGELOG
  • Tests: drain-request.test.ts, demo-banner.test.ts, deployment.test.ts, env.test.ts, tests/integration/{drain,drain-route,intake}.test.ts

Nachweis (SYSTEM.md §11)

  • verify:changed: grün
  • verify: grün nach Review und Merge von main – unit 188, integration 125
  • verify:full / E2E-Spec: review-smoke, line-items-smoke 2/2 grün
  • Manueller Prüfnachweis: –
  • Frischer Review (P1 vor Ready-for-review; Architektur/API/DB immer): feat(deploy): prepare the Vercel showcase with Supabase (#59) #64 (comment) – nichts blockierend, alle should behoben

Doku-Entscheidung (genau eine)

  • Keine langlebige Doku betroffen – Begründung: –
  • Doku betroffen und im selben PR aktualisiert:
    • Produktdoku (P0: README): –
    • Technische Doku: docs/technical/deployment-vercel.md, operations.md
    • Architekturkarte: docs/technical/architecture.md
    • ADR: docs/decisions/ADR-0001-pilot-architecture.md (Amendment D11)
    • CHANGELOG [Unreleased] (sichtbares Feature oder Verhalten – im selben PR, nie „später")

Entferntes oder Umbenanntes: Worker-Verdrahtung nach src/job-drain.ts verschoben; Registereintrag „Upload ohne Rate-Limit“ ersetzt; docs/ gegrept

Dateigrößen und neue Bausteine (SYSTEM.md §7)

Dateien über 500 Zeilen im Diff (Ausnahmen: generierter Code, Lockfiles, Fixtures, Migrationen, Schemas, Ressourcen, Doku, Konfiguration):

  • keine
  • bewusst belassen – Begründung: –
  • im selben PR nach fachlicher Verantwortung geteilt
  • Folge-Issue –

Über 800 Zeilen mit neuer Fachlogik oder über 1000 Zeilen (P1/P2): nicht betroffen

Neue Shared-Komponente, Utility-Datei, Adapter oder fachlicher Service:

  • nein
  • ja – gesucht nach: bestehender Drain-Logik (drain, drainExports, src/worker.ts) und Rate-Limits (Better Auth rate_limit gilt nur für Auth-Routen); gefunden und wiederverwendet – src/job-drain.ts bündelt nur die Verdrahtung aus dem Worker; das Upload-Limit zählt vorhandene requests-Zeilen statt einer neuen Tabelle

Subagent-Einsätze

  • Umsetzungs-Subagent (Worktree) – Umsetzung, Tests, Doku.
  • Frischer Review-Subagent (read-only) – nichts blockierend, 3 should + 7 nits; Umgang im PR-Kommentar.

Risiken / offene Punkte

🤖 Generated with Claude Code

https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1

…rverless drain budget (#59)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
…t a worker (#59)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
…document new variables (#59)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
…contract (#59)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
…Fluid compute (#59)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
… own time, role-level statement timeout (#59 review)

- UPLOAD_MAX_PER_HOUR (required with APP_ENV=showcase): 429 before anything is stored
- after() drains subtract the time the invocation already spent
- bootstrap: passwords via \prompt (not on the command line), ALTER ROLE app_rw SET statement_timeout
- Bearer scheme case-insensitive; runbook and ADR details

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1
…pabase-59

# Conflicts:
#	CHANGELOG.md
#	src/worker.ts

Fluory commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Frischer Review (read-only Subagent) – Ergebnis und Umgang

Nichts blockierend. Bestätigt: Drain-Route mit Vergleich in konstanter Zeit, 404 ohne CRON_SECRET, 401 sonst, kein Secret in Logs oder Antworten; Exactly-once unverändert (dieselben drain()/drainExports(), pg-boss fetch, Row-Lock, Idempotency-Key, eindeutige Export-Zeile); Worker-Refactor verhaltensgleich, Modulgrenzen und Architekturkarte aktualisiert; Supabase-Angaben (S3-Endpoint, Region, Path-Style, Pooler-Benutzernamen, Session-Pooler für Migrationen, NOBYPASSRLS-Rollen, täglicher Hobby-Cron) stimmen.

Befund Umgang
should – statement_timeout als Startparameter erreicht über Supavisor evtl. die Verbindungen nicht (Sicherheitsnetz des Export-Row-Locks) behoben: ALTER ROLE app_rw SET statement_timeout = '30s' im Bootstrap-Skript; Smoke-Check liest ihn
should – Ausnahme „Upload ohne Rate-Limit“ läuft mit dem öffentlichen Deploy ab; Uploads lösen bezahlte KI-Aufrufe aus behoben: UPLOAD_MAX_PER_HOUR pro Person, 429 bevor etwas gespeichert wird, Pflicht bei APP_ENV=showcase; Integrationstest; Registereintrag aktualisiert
should – Zeitbudget ignoriert die Zeit vor dem Drain (Upload im selben Aufruf) behoben: after()-Drains und die Route ziehen die bereits verbrauchte Zeit vom 50-s-Fenster ab; Unit- und Integrationstest
nit – Bearer case-sensitiv behoben (RFC 7235), Test
nit – Passwörter per -v im Prozess-Listing behoben: \prompt ohne Echo, Mindestlänge 16
nit – Cron-Zeitpunkt, graphql_public, Pooler-Sicherheit im ADR, #63-Verweis behoben in Runbook/ADR
nit – Konfigurationsfehler der Route antwortet 503 auch ohne Secret bleibt: verrät nur, dass die Route existiert; Details stehen nur im Log

Nach den Änderungen und dem Merge von main (inkl. #54: Positionen im Export-Drain verdrahtet): verify grün (unit 188, integration 125), beide E2E-Smokes grün.


Generated by Claude Code

…pabase-59

# Conflicts:
#	CHANGELOG.md
#	docs/technical/architecture.md
#	src/features/requests/index.ts
@Fluory
Fluory marked this pull request as ready for review September 24, 2026 17:10
@Fluory
Fluory merged commit 2382244 into main Sep 24, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prepare the Vercel showcase with Supabase (Postgres + Storage) instead of Neon + R2

2 participants