Skip to content

chore(app): TS app skeleton, docker compose and verify commands - #21

Merged
Fluory merged 13 commits into
mainfrom
claude/chore-app-skeleton-3
Sep 23, 2026
Merged

Fluory merged 13 commits into
mainfrom
claude/chore-app-skeleton-3

Conversation

@Fluory

@Fluory Fluory commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

Warum

Fixes #3 · Teil von Epic #2. Basis ist main (Fundament #20 ist gemergt). Alle folgenden Issue-PRs des Nachtlaufs stapeln auf diesem Branch.

Arbeitsstand

  • Ziel: Das leere System wird lauffähig und prüfbar: docker compose up startet Postgres, SeaweedFS, setup, web und einen No-op-worker; verify:changed / verify / verify:full existieren und laufen in der CI.
  • Nicht-Ziele: Auth, RLS-Policies, AI-Service, pg-boss, UI über eine Platzhalterseite hinaus.
  • Erledigt: alles aus dem Testplan; frischer Review eingearbeitet (Kommentar); pnpm verify lokal grün; CI check + compose-smoke grün auf dba107f.
  • Offen: menschliche Freigabe (Migration, Infra/CI, öffentlicher Endpunkt) und Entscheidung „Rollen im Init-Skript".
  • Annahmen: siehe Impact Manifest „Offene Annahmen".
  • Nächster kleinster Schritt: Review durch den Orchestrator; danach Merge, dann feat(identity,tenancy): invite-only login, companies and forced RLS #31 zeigt auf main.

Was ist passiert (Klartext)

Das Projekt hat jetzt ein lauffähiges technisches Gerüst. Ein Befehl (docker compose up) startet die Datenbank, den Dokumentenspeicher, einen einmaligen Einrichtungsschritt (Datenbankstruktur anlegen, privaten Speicherbereich anlegen), die Webanwendung und den Hintergrund-Worker. Unter /api/health sieht man, ob Datenbank und Speicher erreichbar sind – ohne dabei Adressen oder Benutzernamen zu verraten.

Die Datenbank kennt zwei Rollen: eine, die die Struktur ändern darf (nur beim Einrichten), und eine für den laufenden Betrieb, die die spätere Mandantentrennung (Row-Level Security) nie umgehen kann. Die Einrichtung bricht ab, wenn diese Rollen falsch konfiguriert sind.

Außerdem gibt es jetzt die Prüfbefehle, die jeden weiteren PR absichern, und eine automatische Architekturprüfung: Module dürfen nur über ihre öffentliche Eingangsdatei miteinander sprechen, und nur die Datenbankschicht darf die rohe Datenbankverbindung benutzen.

Infrastruktur-/CI-Änderung (braucht menschliche Freigabe): Die CI startet jetzt PostgreSQL und SeaweedFS über compose.yaml, damit die Integrationstests gegen echte Dienste laufen, und ein neuer Job compose-smoke baut das Image und prüft docker compose up → /api/health, aber nur wenn sich Dockerfile oder Compose-Datei ändern. Bestehende Prüfschritte wurden nicht verändert.

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: Root-Konfiguration, src/app (Layout, Platzhalterseite, api/health, Composition Root _server), src/db, src/config, src/features/*/index.ts, storage, observability, jobs (No-op-Worker), compose.yaml, Dockerfile, docker/postgres/init/, .env.example, CI.
  • Schnittstellen / Datenänderungen: neuer Endpunkt GET /api/health; zwei DB-Rollen (app_owner, app_rw); erste Migration legt Schema app + Rechte an.
    • GET /api/health → 200 {"status":"ok","checks":{"database":"ok","storage":"ok"}}; bei Ausfall 503 mit Namen der Prüfung (database, storage, config), ohne Fehlerdetails. Vertrag: docs/technical/api.md.
    • Migration 0000_app_schema: prüft Rollen (kein Superuser, kein BYPASSRLS, keine geerbten Owner-/Bypass-Rollen, läuft als app_owner), CREATE SCHEMA app AUTHORIZATION app_owner, USAGE für app_rw ohne CREATE, Default-Privileges (DML auf Tabellen, Sequenzen) für app_rw.
  • Akzeptanzkriterien: die aus chore(app): TS app skeleton, docker compose and verify commands #3, mit zwei offengelegten Anpassungen (siehe unten „Abweichungen").
  • Testplan: Unit (test-first) für Health-Aggregation und Config; Integration gegen echtes Postgres + SeaweedFS (Health 200/503, Rolleneigenschaften, Schema-Rechte, Rollen-Guard); Architekturtest mit Fixtures (Deep-Import relativ und per @/, roher DB-Client, eigene DB-Verbindung im Feature werden abgelehnt); Compose-Smoke in CI.
  • Verifizierte Fakten: npm 2026-09-22: next 16.3.6, typescript 6.0.3 (TS 7 wegen typescript-eslint-Peer ausgeschlossen), eslint 9.39.5, vitest 5.0.1, drizzle-orm 0.45.3, drizzle-kit 0.31.11, pg 8.23.0, dependency-cruiser 18.4.0, @aws-sdk/client-s3 3.1138.0, zod 4.6.5, tsx 4.23.15 (MIT, Node ≥ 18); SeaweedFS 4.47 lehnt falsche S3-Secrets mit 403 ab (Integrationstest); eslint-config-next 16 exportiert Flat-Config-Arrays; pnpm legt .bin/tsx als Shell-Shim an → im Image node …/tsx/dist/cli.mjs.
  • Offene Annahmen: keine mehr zum Image – lokal nicht baubar (Docker Hub 429), aber durch den CI-Job compose-smoke bewiesen.
  • Nicht-Ziele: Auth, RLS, AI-Service, pg-boss, UI.
  • Risiken und Rollback: neue Dependencies (Supply-Chain) → pnpm audit --audit-level high in verify + Lockfile; Rollback per Revert-PR, lokal docker compose down -v (docs/technical/operations.md).

Abweichungen von den Akzeptanzkriterien (offengelegt)

  1. Rollen entstehen im Init-Skript, nicht in der Migration. Sonst stünden Passwörter im Repo oder die Migration bräuchte Superuser-Rechte. Die Migration prüft die Rollen und bricht sonst ab; außerhalb Docker legt ein Operator die Rollen einmalig an (operations.md). → decision-needed für den Orchestrator (Morgenbericht).
  2. pg-boss kommt erst mit feat(intake): upload a request and enqueue processing atomically #5/feat(jobs,extraction): process requests in the worker with retries and visible errors #7 (Worker ist hier No-op) – keine Dependency auf Vorrat.

Neue Dependencies (Begründung)

Paket Version Lizenz Warum
next, react, react-dom 16.3.6 / 19.3.0 MIT App-Framework laut ADR-0001 D1
drizzle-orm, drizzle-kit, pg 0.45.3 / 0.31.11 / 8.23.0 Apache-2.0 / MIT ORM + SQL-Migrationen laut D3
@aws-sdk/client-s3 3.1138.0 Apache-2.0 S3-API laut D5
zod 4.6.5 MIT Konfigurationsvalidierung
tsx 4.23.15 MIT führt Worker und Deploy-Schritt aus den TS-Quellen aus (Pfad-Aliasse; Node-Type-Stripping löst @/ nicht auf)
vitest, dependency-cruiser, eslint 9, eslint-config-next, typescript 6.0.3 s. o. MIT Tests, Architekturprüfung, Lint, Typen

Geändert

  • package.json, pnpm-lock.yaml, tsconfig.json, eslint.config.mjs, next.config.ts, vitest.config.ts, .dependency-cruiser.cjs, drizzle.config.ts: Toolchain und Prüfbefehle.
  • src/app/: Layout, Platzhalterseite, api/health/route.ts, _server/runtime.ts (Pool + Storage-Client, lazy).
  • src/db/: Client, Migrator, Schema-Stub, Migration 0000_app_schema.sql.
  • src/features/*/index.ts: Modul-Skelette laut Architekturkarte; storage (S3-Adapter: ping, ensureBucket), observability (Health).
  • src/setup.ts (Deploy-Schritt: Migrationen + Bucket, Retry nur bei transienten Fehlern), src/worker.ts (No-op).
  • compose.yaml (+ setup, web, worker), Dockerfile, .dockerignore, .env.example, scripts/verify-changed.sh.
  • .github/workflows/ci.yml: Postgres + SeaweedFS vor pnpm verify; Job compose-smoke.
  • Tests: tests/integration/* (Health, Migration, Rollen-Guard), tests/architecture/dependency-rules.test.ts + Fixtures.
  • Doku: AGENTS.md (Fundament-Hinweis entfernt), README, Architekturkarte, docs/technical/operations.md, docs/technical/api.md, CHANGELOG.

Nachweis (SYSTEM.md §11)

Doku-Entscheidung (genau eine)

  • Keine langlebige Doku betroffen – Begründung: –
  • Doku betroffen und im selben PR aktualisiert:
    • Produktdoku (P0: README): README.md (Getting started)
    • Technische Doku: docs/technical/operations.md (Start/Stopp/Rollback, Rollen außerhalb Docker), docs/technical/api.md (Health-Vertrag), AGENTS.md (Befehle)
    • Architekturkarte: docs/technical/architecture.md (Modulstatus, app-Zeile, DB-Rollen)
    • ADR: –
    • CHANGELOG [Unreleased] (sichtbares Feature oder Verhalten – im selben PR, nie „später")

Entferntes oder Umbenanntes: docs/ + README gegrept, Treffer bereinigt: Fundament-Hinweis in AGENTS.md und README-Status ersetzt

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: vorhandenem Code im Repo und Modulen der Architekturkarte; gefunden: nichts – storage (S3-Adapter) und observability (Health) sind in der Karte vorgesehen und entstehen hier erstmals

Subagent-Einsätze

Risiken / offene Punkte

  • docker compose up lokal nicht ausführbar (Docker Hub 429) – Beweis über CI-Job compose-smoke (grün).
  • Offene Entscheidung: Rollen im Init-Skript statt in der Migration (s. Abweichungen).

🤖 Generated with Claude Code

https://claude.ai/code/session_01DJ5vaKvTYiMvdngT4d3xo1

Fluory and others added 3 commits September 22, 2026 22:55
The impact manifest lives in the PR description (plan before code, SYSTEM.md §4).

Refs #3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… leaks

/api/health will be reachable without login, so a failed check must be
named but never described (no hosts, users or driver messages). A check
that hangs counts as failed after its time limit instead of blocking the
endpoint. Toolchain pinned: TypeScript 6.0.3 because typescript-eslint
does not accept TS 7 yet; ESLint 9 because eslint-plugin-react and
jsx-a11y (via eslint-config-next) do not accept ESLint 10 yet.

Refs #3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Environment variables are an outside boundary: missing or malformed
values must stop the start with a clear message. The message names the
variables but never their values, because values are secrets and error
messages end up in logs. New module `config` added to the map first.

Refs #3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Fluory
Fluory changed the base branch from claude/chore-foundation-1 to main September 22, 2026 21:12
Two roles per ADR-0001 D7: app_owner runs migrations, app_rw is the
runtime role without superuser or BYPASSRLS, so RLS always applies.
Passwords come from the environment (local-only defaults in compose),
never from a migration. The S3 identity is rendered from env at start.

Verified locally 2026-09-22 (Docker 28.4): roles exist with
rolsuper=false, rolbypassrls=false; SeaweedFS 4.47 accepts the
configured credentials and answers 403 to a wrong secret.

Refs #3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Fluory

Fluory commented Sep 22, 2026

Copy link
Copy Markdown
Owner Author

Übergabe

Status: WIP – übergeben an die Cloud-Implementierungs-Session (Nachtlauf 2026-09-22/23)
Erledigt: Plan (Impact Manifest); Toolchain gepinnt (TS 6.0.3, ESLint 9.39.5, Next 16.3.6, Vitest 5.0.1, Drizzle 0.45.3); observability/health.ts (4 Unit-Tests) und config/env.ts (5 Unit-Tests) test-first; compose.yaml (Postgres 17.11 + SeaweedFS 4.47) und docker/postgres/init/01-roles.sh, lokal verifiziert: app_owner/app_rw ohne Superuser/BYPASSRLS; SeaweedFS antwortet mit 403 auf ein falsches Secret.
Offen: ESLint-Config, Next-App (layout, Platzhalterseite, api/health-Route mit DB- und Storage-Check), src/db (Client, Drizzle-Config, erste Migration: Schema app + Grants für app_rw), Bucket-Anlage (idempotent), Modul-Skelette + dependency-cruiser inkl. Negativtest, No-op-Worker, Dockerfile + web/worker in Compose, .env.example, scripts/verify-changed.sh, CI mit Postgres + SeaweedFS für Integrationstests, AGENTS.md-Fundamenthinweis entfernen, Architekturkarte/README.
Nächster Schritt: Integrationstest (rot) für /api/health gegen Compose-Postgres + SeaweedFS.
Risiko: keins bekannt; Tests grün (9/9).

- dependency-cruiser: features may not open DB connections; imports must resolve; alias fixture
- health route logs config errors (variable names only)
- setup retries only transient errors; migration guard also checks inherited roles, with a test
- docs: api.md (health contract), roles outside Docker, verify:changed wording

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

Fluory commented Sep 23, 2026

Copy link
Copy Markdown
Owner Author

Frischer Review (review-pr, unabhängiger Subagent) – Befunde und Auflösung

Review lief read-only gegen den Branch-Stand; zwei Durchläufe (der erste fand einen Blocker im Compose-Pfad, der zweite prüfte den Fix). Auflösung in Commit dba107f (plus 6aec57e, b3b3c03 für den Compose-Pfad).

# Befund Auflösung
Blocker (1. Lauf) compose-smoke rot: node node_modules/.bin/tsx ist bei pnpm ein Shell-Shim behoben – node node_modules/tsx/dist/cli.mjs; compose-smoke jetzt grün
Important Startup-Race: setup lief, bevor SeaweedFS antwortete behoben – begrenzte Wiederholung (30 × 2 s) nur für Verbindungsfehler
Important compose-smoke grün noch nicht belegt erledigt – check + compose-smoke grün auf dba107f
Important depcruise: Features konnten @/db importieren und selbst eine Verbindung öffnen; _server-Ausnahme unnötig behoben – Regel no-db-connection-in-features (nur Typ-Importe erlaubt), Ausnahme entfernt, Fixture + Negativtest
Important Health-Route loggte Konfigurationsfehler nicht, obwohl operations.md das zusagt behoben – Log nennt Variablennamen (nie Werte)
Note Retry auch bei dauerhaften Fehlern (Rollen-Guard, falsche Credentials) behoben – nur transiente Fehler werden wiederholt
Note Rollen-Guard der ersten Migration ohne Test; geerbte Rollen nicht geprüft behoben – Integrationstest führt den Guard als app_rw aus; Guard prüft zusätzlich pg_auth_members
Note Health-Vertrag nur im PR; kein Rate Limit behoben – docs/technical/api.md; Rate Limit/Cache vor öffentlicher Bereitstellung (Showcase #19) dokumentiert
Note kein not-to-unresolvable, keine Alias-Fixture behoben – Regel + @/-Fixture mit Negativtest
Note Rollen außerhalb Docker undokumentiert; verify:changed versprach „format" behoben – operations.md-Abschnitt; AGENTS.md-Formulierung korrigiert (ESLint inkl. Stilregeln, kein separater Formatter)

Offen für den Menschen: Rollen entstehen im Init-Skript statt in der Migration (Abweichung vom Akzeptanzkriterium, begründet) – decision-needed im Morgenbericht. Verdict des Reviewers: mergebar nach menschlicher Freigabe (Migration, Infra/CI, öffentlicher Endpunkt).


Generated by Claude Code

@Fluory
Fluory marked this pull request as ready for review September 23, 2026 06:04
@Fluory
Fluory merged commit a89fd78 into main Sep 23, 2026
4 of 6 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.

chore(app): TS app skeleton, docker compose and verify commands

2 participants