Some checks are pending
CI / validate (push) Waiting to run
Citizen-Science-App für botanische Beobachtungen — föderierte App des mana e.V.-Ökosystems. Erste Vereins-App mit 3-Schicht-Datenmodell (Species/Specimen/Observation), 3-Stufen-GPS (exact/rounded_100m/ region_only) und fail-closed Sensitivity-Schutz für FFH-Anhang-IV- Arten. Stand η-0 (Plan v0.2 + Detail-Pass, Architect+Compliance-Audit 2026-05-17): - Workspace: pnpm/turbo/tsconfig/prettier - apps/api: Hono+Bun mit /healthz, /readyz, /healthz/details, /.well-known/mana-app.json. Smoke alle vier Endpoints grün. - apps/web: SvelteKit 2 + Svelte 5 + Tailwind v4, Herbarium-Theme, Landing-Page mit Phasen-Plan. svelte-check 0 errors / 0 warnings. - docker-compose: postgis/postgis:16-3.4 auf 5449, init-postgis.sql - app-manifest.json: validiert gegen @mana/shared-share-protocol@0.4.0 (2 shares, 2 accepts, 3 tools) - Doku: CLAUDE.md, README.md, STATUS.md Cross-Repo: @mana/shared-share-protocol@0.4.0 published mit 4 neuen Share-Types (mana/photo, mana/geo-point, mana/plant-observation, mana/plant-specimen). Plan: mana/docs/playbooks/HERBATRIUM_GREENFIELD.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
198 lines
9.2 KiB
Markdown
198 lines
9.2 KiB
Markdown
# CLAUDE.md — Herbatrium
|
||
|
||
Guidance für Claude Code in diesem Repo.
|
||
|
||
> **Wenn du gerade neu bist:** lies zuerst [`STATUS.md`](STATUS.md) — dort
|
||
> steht der aktuelle Phasen-Stand. Dieses CLAUDE.md ist nur die
|
||
> Konventions- und Architektur-Referenz. Der vollständige Plan lebt in
|
||
> [`../mana/docs/playbooks/HERBATRIUM_GREENFIELD.md`](../mana/docs/playbooks/HERBATRIUM_GREENFIELD.md).
|
||
|
||
## Was dieses Repo ist
|
||
|
||
**Herbatrium** — Citizen-Science-App für botanische Beobachtungen in
|
||
freier Natur. Föderierte App des **mana e.V.**-Ökosystems. Erste
|
||
Vereins-App mit **3-Schicht-Datenmodell** (Species/Specimen/Observation),
|
||
**3-Stufen-GPS-Sichtbarkeit** (`exact`/`rounded_100m`/`region_only`)
|
||
und **fail-closed Sensitivity-Schutz** für FFH-Anhang-IV-Arten.
|
||
|
||
```
|
||
HTTPS ┌──────────────────┐
|
||
mana e.V. ◄─────────────── │ herbatrium/ │ Postgres + PostGIS
|
||
Plattform │ (this repo) │ herbatrium.mana.how
|
||
• mana-auth (JWKS) │ SvelteKit-Web │
|
||
• mana-llm (primär ID) │ + Hono-API │
|
||
• mana-media (Photos) └──────────────────┘
|
||
• mana-geocoding (Reverse) ▲
|
||
• mana-share/links/events │ HTTPS
|
||
• mana-mcp (Tools) │
|
||
┌─────────┴────────┐
|
||
│ Pl@ntNet API │
|
||
│ (AGPV / EU) │
|
||
│ Sekundär-Verifier│
|
||
└──────────────────┘
|
||
┌──────────────────┐
|
||
│ GBIF / IUCN / │
|
||
│ FFH-Listen │
|
||
│ (Sensitivity) │
|
||
└──────────────────┘
|
||
```
|
||
|
||
## Status
|
||
|
||
**Phase η-0 — Repo-Skelett (2026-05-17).** Manifest + API/Web-
|
||
Boilerplate stehen, DB-Schema und Klassifikations-Pipeline noch nicht
|
||
implementiert. Roadmap siehe [`STATUS.md`](STATUS.md). Vollständiger
|
||
Plan in
|
||
[`../mana/docs/playbooks/HERBATRIUM_GREENFIELD.md`](../mana/docs/playbooks/HERBATRIUM_GREENFIELD.md).
|
||
|
||
## Architektonische Invarianten
|
||
|
||
Beschlossen. Nicht ohne explizite Diskussion antasten.
|
||
|
||
1. **Fail-closed Sensitivity.** Jede Species startet
|
||
`is_sensitive=true` bis ein erfolgreicher GBIF-IUCN/FFH-Lookup das
|
||
Gegenteil belegt. Sensitive Specimens werden niemals mit
|
||
100 m-GPS public — nur als Region (admin_level=4, ~30–50 km).
|
||
Verhindert BNatSchG-§44-Risiko.
|
||
2. **Pl@ntNet-Outbound nur `display`-Layer.** Niemals `original/` mit
|
||
GPS-EXIF an Drittdienste. Festgenagelt durch Compliance-Audit
|
||
2026-05-17.
|
||
3. **mana-llm primär, Pl@ntNet sekundär (Verifier).** Reihenfolge-
|
||
Entscheidung 2026-05-17: LLM-Vision liefert Top-3 + Phenology in
|
||
einem Pass; Pl@ntNet wird zugeschaltet bei niedrigem Confidence
|
||
oder Sensitivity-Verdacht. Spart Quota.
|
||
4. **3-Layer-Foto-Storage (original/display/thumb)**. Original
|
||
verlustfrei mit EXIF, Display EXIF-stripped 2048px, Thumb 512px.
|
||
Jeder Original-Access loggt in `photo_original_access_log`.
|
||
5. **Lizenz-Picker beim Publish, kein stiller Default.** Default
|
||
`license=null`, beim Publish-Step explizit CC-BY-SA / CC-BY-NC /
|
||
ARR wählen. Daten-Souveränität-Prinzip.
|
||
6. **Eigene Postgres-DB `mana_herbatrium` mit PostGIS** (Image
|
||
`postgis/postgis:16-3.4`, Port 5449). Schema-Isolation via
|
||
`pgSchema('herbatrium')`.
|
||
7. **Server-authoritative MVP.** Keine Dexie. Frontend = HTTP-Client.
|
||
Local-First nachrüstbar via mana-sync in Phase η-9.
|
||
8. **Web ist SvelteKit 2 + Svelte 5 (runes)**, API ist Hono+Bun+
|
||
Drizzle. Plattform-Standard.
|
||
9. **Tier-Gate:** `public` lesen, `beta` schreiben. Beta+alpha+founder
|
||
dürfen Observations anlegen.
|
||
|
||
## Stack
|
||
|
||
| Layer | Wahl |
|
||
|---|---|
|
||
| Web | SvelteKit 2 + Svelte 5 (runes) + Tailwind v4 |
|
||
| API | Hono + Bun |
|
||
| Datenbank | PostgreSQL + PostGIS + Drizzle ORM |
|
||
| Auth | JWKS aus mana-auth, ab η-1 |
|
||
| Föderation | `@mana/shared-app-tpl` ab η-5 |
|
||
| Klassifikation | mana-llm (primär) + Pl@ntNet (Verifier) |
|
||
| Geo | `mana-geocoding`, PostGIS `ST_DWithin` |
|
||
| Storage | `mana-media` (3-Layer: original/display/thumb) |
|
||
| Map | MapLibre + Cloudflare-Pull-through-OSM |
|
||
|
||
Ports: API `3101`, Web `3102`, Postgres `5449` (siehe
|
||
[`../mana/docs/PORTS.md`](../mana/docs/PORTS.md)).
|
||
|
||
## Repo-Struktur
|
||
|
||
```
|
||
herbatrium/
|
||
├── apps/
|
||
│ ├── api/ Hono+Bun-Backend (Port 3101)
|
||
│ │ └── src/
|
||
│ │ ├── index.ts App-Boot
|
||
│ │ ├── routes/
|
||
│ │ │ ├── health.ts /healthz, /readyz, /healthz/details
|
||
│ │ │ └── manifest.ts /.well-known/mana-app.json
|
||
│ │ ├── db/schema/ Drizzle, pgSchema('herbatrium'), folgt η-1
|
||
│ │ ├── lib/ plantnet, gbif, photo-pipeline, …
|
||
│ │ ├── share-handlers/ save_photo_as_observation, …
|
||
│ │ └── middleware/ JWT, service-key
|
||
│ └── web/ SvelteKit-Frontend (Port 3102)
|
||
│ └── src/
|
||
│ ├── app.html, app.css (Herbarium-Theme)
|
||
│ ├── routes/
|
||
│ │ ├── +layout.svelte
|
||
│ │ └── +page.svelte Landing-Stub mit Phasen-Anzeige
|
||
│ └── lib/ api-clients, stores, components
|
||
├── packages/ leer, app-eigene Pakete später
|
||
├── infrastructure/
|
||
│ └── init-postgis.sql CREATE EXTENSION postgis (Container-Init)
|
||
├── docs/ repo-eigene Doks (PHOTO_PIPELINE, GPS_PRIVACY, …)
|
||
├── app-manifest.json Föderations-Manifest
|
||
├── docker-compose.yml lokales PostGIS :5449
|
||
├── STATUS.md aktueller Phasen-Stand
|
||
└── .github/workflows/ ci.yml
|
||
```
|
||
|
||
## Föderations-Vertrag
|
||
|
||
Pflicht-Endpoints (alle ab η-5 via `@mana/shared-app-tpl`):
|
||
|
||
| Method | Path | Phase |
|
||
|---|---|---|
|
||
| `GET` | `/healthz`, `/readyz`, `/healthz/details` | η-0 (jetzt) |
|
||
| `GET` | `/.well-known/mana-app.json` | η-0 (jetzt) |
|
||
| `POST` | `/api/v1/photos` | η-1 |
|
||
| `POST` | `/api/v1/observations` | η-1 |
|
||
| `POST` | `/api/v1/identify` | η-2 |
|
||
| `GET` | `/api/v1/specimens?near=…` | η-3 |
|
||
| `GET` | `/api/v1/public/feed` | η-4 |
|
||
| `POST` | `/api/v1/share/receive` | η-5 |
|
||
| `POST` | `/api/v1/tools/:name` | η-5 |
|
||
| `GET` | `/api/v1/dsgvo/export` | η-5 |
|
||
| `POST` | `/api/v1/takedown` | η-7 |
|
||
|
||
## Konventionen
|
||
|
||
- **pnpm 9.15.x**, Node 20+, Turborepo 2.x, Bun für API-Runtime
|
||
- **Tabs** für Indent, single quotes, 100-col Prettier
|
||
- **Drizzle 0.38 / drizzle-kit 0.30** (Plattform-Einklang)
|
||
- **JWT-Validation lokal** über JWKS-Cache (5 min), kein Live-Call
|
||
- **Service-to-Service** via `X-Service-Key` (`MANA_SERVICE_KEY`)
|
||
- **Tests**: Vitest
|
||
- **PostGIS-Geometry-Felder**: Drizzle `customType<{ data: string }>`
|
||
für `geometry(Point, 4326)`-Spalten
|
||
|
||
## Sensitivity-Disziplin (Pflicht ab η-1)
|
||
|
||
Jeder species-Insert muss durch `lib/gbif.ts` (folgt η-2) gehen:
|
||
|
||
- GBIF-Match auf scientific_name → IUCN-Category (`CR`/`EN`/`VU`/...)
|
||
- FFH-Anhang-IV-Liste querchecken (statische Liste, `lib/ffh-list.ts`)
|
||
- Setzt `is_sensitive`, `iucn_category`, `ffh_annex_iv`,
|
||
`sensitivity_source`, `sensitivity_resolved_at`
|
||
- Bei Fehler oder Timeout: `is_sensitive=true` bleibt stehen (fail-closed)
|
||
|
||
Sensitive Specimens dürfen NIE mit GPS-Marker public sein — nur als
|
||
Region-Cluster. Compliance-Audit hängt von dieser Disziplin ab.
|
||
|
||
## Lokal entwickeln
|
||
|
||
```bash
|
||
pnpm install # @mana/* aus pkg.mana.how
|
||
docker compose up -d # PostGIS :5449
|
||
pnpm --filter ./apps/api dev # API :3101
|
||
pnpm --filter ./apps/web dev # Web :3102
|
||
```
|
||
|
||
Voraussetzungen:
|
||
- mana-Plattform-Stack lokal laufend (mindestens `mana-auth` :3001;
|
||
`mana-llm`, `mana-media`, `mana-geocoding` für Klassifikation +
|
||
Foto-Pipeline + Reverse-Geocode)
|
||
- Verdaccio-Token in `~/.npmrc`
|
||
(`npm login --registry=https://pkg.mana.how`)
|
||
- Pl@ntNet-API-Key in `.env.local` (Sekundär-Verifier; ohne läuft die
|
||
App, nur LLM-Klassifikation; siehe Playbook „Operative Helfer")
|
||
|
||
## Wichtige Cross-Repo-Doks
|
||
|
||
- [`../mana/docs/playbooks/HERBATRIUM_GREENFIELD.md`](../mana/docs/playbooks/HERBATRIUM_GREENFIELD.md) — vollständiger Plan v0.2
|
||
- [`../mana/docs/FEDERATION.md`](../mana/docs/FEDERATION.md) — Architektur-Grundlage
|
||
- [`../mana/docs/PORTS.md`](../mana/docs/PORTS.md) — Port-Allokation
|
||
- [`../mana/docs/COMPLIANCE.md`](../mana/docs/COMPLIANCE.md) — DSGVO, BNatSchG, DSA
|
||
- [`../mana/docs/AGGREGATOR_POLICY.md`](../mana/docs/AGGREGATOR_POLICY.md) — Drittdienst-Disziplin (Pl@ntNet-Geist)
|
||
- [`../mana/services/mana-llm/CLAUDE.md`](../mana/services/mana-llm/CLAUDE.md) — primärer Klassifikator
|
||
- [`../mana/services/mana-media/CLAUDE.md`](../mana/services/mana-media/CLAUDE.md) — Foto-Storage
|
||
- [`../mana/services/mana-geocoding/CLAUDE.md`](../mana/services/mana-geocoding/CLAUDE.md) — Reverse-Geocode
|