Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21 KiB
Herbatrium — Status
Lebt-Doku. Bei Phasen-Wechsel hier aktualisieren, nicht im Playbook. Das Playbook ist der ursprüngliche Plan, dieses STATUS.md zeigt, wo wir tatsächlich stehen.
Rechtsklick-Kontextmenü auf Beobachtungen (2026-06-03)
Geteiltes @mana/shared-ui/organisms ContextMenu auf jeder Observation
(/my/observations): Status setzen (Phänologie via
updateObservationState: blühend/fruchtend/…) + Zurückziehen
(unpublishObservation, nur wenn nicht privat) + Löschen. Veröffentlichen
bewusst NICHT im Menü — braucht den Lizenz-Picker (Invariante 5, kein
stiller Default; besonders heikel bei sensiblen Arten). svelte-check 0
Fehler, build grün, Screenshot verifiziert, live auf app.herbatrium.com.
(0643fd1)
Hinweis: Die öffentliche Landing (
/) zeigt noch „IM AUFBAU · η-0", obwohl/plants+/my/observationsreal laufen — Landing-Text hängt dem tatsächlichen Stand hinterher.
Aktuelle Phase: Welle-1-Landing-Cutover LIVE (2026-05-22)
✅ 2026-05-22 20:00 — Subdomain-Migration (Landing-Cutover)
LANDING_CUTOVER.md
ausgeführt. herbatrium.com zeigt jetzt auf die Astro-Marketing-
Landing (herbatrium-landing:3204); die App lebt unter
app.herbatrium.com (herbatrium-web:3104, war vorher
herbatrium.com).
GET https://herbatrium.com/→ 200 Marketing-Landing (Title: „herbatrium — Pflanzen-Tagebuch mit Naturschutz-Disziplin")GET https://herbatrium.com/{ueber,mitwirken}→ 200GET https://herbatrium.com/sitemap.xml→ 200 (4 Marketing-URLs)GET https://herbatrium.com/robots.txt→ 200, Allow + Sitemap- ReferenzGET https://herbatrium.com/.well-known/apple-app-site-association→ 200 application/json (Universal Links bleiben funktional)GET https://herbatrium.com/{plants/*,capture,my,auth/*,datenschutz,offline}→ 301 → app.herbatrium.com/...GET https://app.herbatrium.com/→ 200 Web-AppGET https://api.herbatrium.com/healthz→{"status":"ok"}
Offene Punkte aus dem Cutover:
- Native iOS-App-AASA hardcoded auf
herbatrium.com— Universal-Links auf/plants/*werden weiterhin vom Landing-nginx korrekt ausgeliefert (gleicher JSON-Payload, gleiche App-IDs). Im nächsten TestFlight-Build verifizieren. - Erst-Login auf
app.herbatrium.combei einem User, der vorher aufherbatrium.comeingeloggt war: mana-auth-Cookie auf.mana.howist nicht betroffen; App-eigene Cookies auf der alten Apex-Domain verwaisen (falls vorhanden).
Aktuelle Phase: η-3' — Production-Deploy auf Mac Mini LIVE (2026-05-17)
✅ 2026-05-17 — Auth-Integration + Production-Container
Auth (Cards-Pattern portiert):
lib/auth.tsSession-Class mit JWT-Token-LocalStorage, tryRefresh gegen mana-auth/api/v1/auth/refresh, ensureFreshToken vor jedem API-Call, Dev-Stub als localhost-Fallback.lib/api.tsAuthorization: Bearer aus session.token./auth/callback-Route mit Return-URL-Restore.(app)/+layout.svelte„Mit mana-Konto anmelden"-Button, Dev-Stub als<details>nur auf localhost sichtbar.
mana-Plattform live aktualisiert:
services/mana-auth/src/auth/sso-origins.ts: herbatrium.mana.how + herbatrium-api.mana.how zu PRODUCTION_TRUSTED_ORIGINS. Plus localhost:3101/3102.sso-config.spec.ts8/8 grün.services/mana-auth-web/src/lib/apps.ts: herbatrium-Eintrag mit Moos-Grün-Theme (HSL 104 29% 32%).auth.apps-Insert auf mana_platform-DB durch.- mana-auth + mana-auth-web Container neu gebaut + restartet.
CORS_ORIGINSder mana-auth Service-Env in zentraler Compose erweitert.
Container live auf mana-server:
/Users/mana/projects/herbatrium/aus Forgejo geklont.- 3 Container im
manacore-monorepo-Netzwerk:herbatrium-postgres(postgis/postgis:16-3.4) → 127.0.0.1:5449herbatrium-api→ host 3103 (memoro-audio-server belegt 3101)herbatrium-web→ host 3104
- Schema appliziert via
pnpm db:pushim API-Container. - Cloudflared-Config in
managarten/cloudflared-config.ymlerweitert +launchctl stop/start com.cloudflare.cloudflared.
Lokale Smokes (auf mana-server):
curl http://localhost:3103/healthz→ 200 okcurl http://localhost:3103/readyz→ 200 db:okcurl http://localhost:3104/→ 200
🟢 LIVE auf https://herbatrium.mana.how (2026-05-17)
DNS-CNAMEs via cloudflared tunnel route dns angelegt — kein
Dashboard-Klick nötig:
cloudflared tunnel route dns 1435166a-0e3f-4222-8de6-744f32cea5c9 herbatrium.mana.how
cloudflared tunnel route dns 1435166a-0e3f-4222-8de6-744f32cea5c9 herbatrium-api.mana.how
Live-Smoke 2026-05-17 (via 1.1.1.1):
https://herbatrium-api.mana.how/healthz→ 200{"status":"ok"}https://herbatrium-api.mana.how/readyz→ 200{"db":"ok"}https://herbatrium-api.mana.how/.well-known/mana-app.json→ 200 Manifesthttps://herbatrium.mana.how/→ 200
End-to-End-Browser-Test:
- https://herbatrium.mana.how/plants öffnen
- „Mit mana-Konto anmelden" → Redirect zu auth.mana.how/?app=herbatrium
- Mana-Konto-Login (Brand: Moos-Grün)
- Callback → /plants
- „+ Manuell" → Form ausfüllen (Name + Species + GPS + Notes) → Speichern
- Pflanze in Liste sehen, Detail öffnen, Inline-Name editieren
- „+ Neues Foto" → Tracking-Eintrag (ohne Foto klappt; Foto-Upload braucht mana-media erreichbar von herbatrium-api aus)
Aktuelle Phase: η-3' — MVP-Pivot Pflanzen-Tagebuch LIVE (2026-05-17)
✅ η-3' 2026-05-17 — Single-User-Pflanzen-Tagebuch
Begründung Pivot: Till hat den ursprünglichen Citizen-Science-MVP
auf einen Single-User-Pflanzen-Tagebuch-MVP verschoben (analog Nutriphi):
Fotos hochladen → AI klassifiziert → Pflanze anlegen + benennen + tracken.
Public-/Sensitivity-/Compliance-Pipeline bleibt im Schema schlafend
(visibility=private Default), aktiviert sich erst wenn du wirklich
publishen willst.
Schema-Anpassung:
specimen.species_idvon NOT NULL → nullable. Drizzle-Migration 0001 (0001_nebulous_earthquake.sql). Erlaubt Pflanzen anzulegen, bevor AI klassifiziert hat.
API-Routes neu/erweitert:
POST /api/v1/specimens— Pflanze manuell anlegen mitdisplay_label,description,lat/lng, optionalspecies_scientific_name.species_iddarfNULLbleiben bis/identifyaufgerufen wird.PATCH /api/v1/specimens/:id— Name + Beschreibung + Standort editieren. Owner-Check viafirst_seen_by.GET /api/v1/specimens/my— Pflanzen-Liste mit Cover-Photo + Species- Info + Observation-Count. Sortiert nachupdated_atdesc.GET /api/v1/specimens/:id— Detail mit allen Observations + Photos (chronologisch).POST /api/v1/observationserweitert:specimen_idist optional.- Mit
specimen_id: Tracking-Modus — neue Observation an existierende Pflanze,observation_countwird inkrementiert. - Ohne
specimen_id: Create-Modus wie bisher (neues Specimen + Observation in einem).
- Mit
Web-UI komplett umgebaut:
/plants— Karten-Grid mit Cover-Foto, Name, Species, Eintrag-Count./plants/:id— Detail mit Foto-Timeline aller Observations, inline-editierbarer Pflanzen-Name, „+ Neues Foto"-Button./plants/:id/track— Tracking-Page: Foto + Zustand + Notes → an bestehende Pflanze anhängen./plants/new— Foto-First-Flow (Nutriphi-Style):- Foto(s) auswählen + GPS
- „Identifizieren" → POST specimen + POST observation + POST photos + POST identify
- Top-3 LLM-Vorschläge + optional Pl@ntNet-Verifier-Treffer
- User wählt + benennt + speichert
- Navigation umgestellt:
/capture→ 307-Redirect auf/plants/new. /my/observationsbleibt als Legacy-Sicht mit Hinweis-Banner.
Smoke 2026-05-17:
pnpm type-check✓ (API + Web, 0 errors / 0 warnings, 259 Files)
Was im Schema schlafend bleibt (für späteren Citizen-Science-Pivot):
visibility=public/unlisted, Lizenz-Picker (η-4)- FFH/IUCN-Sensitivity-Worker (läuft trotzdem, schadet nicht — kennzeichnet Schutzart-Verdacht in der Detail-View)
geo_visibility=region_onlyfür sensitive speciesphoto_original_access_log,takedown_request(Tabellen da, ohne UI)- Public-Feed, Take-Down-UI
Diese Features sind im Code/Schema, aber UI nicht freigeschaltet. Re-Aktivierung jederzeit per Phase η-4 möglich.
Aktuelle Phase: η-2 — Klassifikation + Sensitivity LIVE (2026-05-17)
✅ η-2 2026-05-17
Klassifikations-Pipeline analog Playbook §Klassifikations-Pipeline.
lib/mana-llm.ts(Port 3025, mana/vision-Alias, OpenAI-kompatibel): display-Layer als base64-data-URL, JSON-Schema-System-Prompt erzwingt{ candidates[1..3], phenology, damage_indicators[], notes }-Output.fetchAsBase64DataUrl()-Helper für mana-media-Display-URLs.lib/gbif.ts(kein Key, höflicher User-Agent):lookupGbif(scientificName)→ match + IUCN-Kategorie + family + lifeForm→growthForm-Mapping. 5min-Cache.lib/ffh-list.ts(~30 Taxa Stand η-2, BfN- + EU-92/43/EEC-Annex-IV):isFfhAnnexIv()mit Genus+Species-Binomial-Match (Subspezies-tolerant).lib/plantnet.ts: Verifier-Client, returnsnullwennPLANTNET_API_KEYleer. Default no-store Outbound (User-Opt-In für CC-BY-SA-Pool kommt in η-4-Lizenz-Picker).lib/sensitivity.tsresolveSpeciesSensitivity()async: ruft GBIF + FFH-Match, setztis_sensitive=falseNUR bei erfolgreichem Lookup ohne Schutz-Indikation. Fail-closed bei Fehler.POST /api/v1/identify:{ observation_id, force_verifier? }. Owner-Check → photos→display→base64 → mana-llm-Vision → optional Pl@ntNet wenn LLM-Confidence < 0.6 oder Sensitivity-Verdacht (FFH-IV- Genera-Heuristik). Combined-Sort mit Pl@ntNet-Tiebreak → chosen. Species-upsert (fail-closed neu, sonst existing), Auto-Confirm wenn confidence ≥ 0.7 UND nicht sensitive, sonstpending.identification_proposal-Rows pro Treffer (Audit-Trail). Sensitivity-Worker async-getriggert (void-Promise).
Smoke 2026-05-17:
pnpm type-check✓ (API + Web)- POST
/api/v1/identifyohne Auth → 401 ✓ - Fail-secure dev-stub bestätigt ✓
Offen für η-2 Live-Smoke:
- mana-llm-Service hochfahren (
docker-compose upinmana/services/mana-llm/) - Mit echtem Foto im DB + Display-URL erreichbar → Identify-Endpoint testen
- Pl@ntNet-Key besorgen (operativ via Playbook §Operative Helfer)
Aktuelle Phase: η-1b — Foto-Upload an mana-media LIVE (2026-05-17)
✅ η-1b 2026-05-17
mana-media-Delegation statt eigener Sharp-Pipeline. Begründung: mana-media (3015) macht selbst Content-Hash-Dedup + 3-Layer-Variants on-the-fly, Lokal kein Sharp-Build nötig.
lib/mana-media.ts— multipart-Upload mitapp=herbatrium+userId+X-Service-Key. URL-Builder für original/display/thumb.display=?w=2048&format=webp— Pflicht für Pl@ntNet-Outbound (WebP-Encode strippt EXIF beim on-the-fly-Transform; Pflicht-Verifikation beim ersten Live-Test).originalbleibt Owner-only, EXIF erhalten.
lib/exif.ts(exifr ^7.1.3): server-side EXIF-Extraktor (taken_at, GPS-Lat/Lng/Alt, Camera-Make/Model, Brennweite, Orientation). Gespeichert inphoto.exif_jsonals JSONB.POST /api/v1/photosPipeline:- Auth + Tier-Check (
beta) - Multipart-Parse + MIME-Whitelist (jpeg/png/heic/webp/avif) + 30 MB Cap
- Owner-Check der
observation_id - EXIF aus Buffer extrahieren
- Upload an mana-media (Content-addressed, Dedup)
- Photo-Row mit
media_idin allen 3 Storage-Keys - Response: 3 URLs + EXIF-JSON
- Auth + Tier-Check (
GET /api/v1/photos?observation_id=...Owner-only-Liste..env.examplekorrigiert:MANA_MEDIA_URL3065 → 3015.
Smoke 2026-05-17:
pnpm type-check✓- POST
/api/v1/photosohne Auth → 401 ✓ - GET
/api/v1/photosohne Auth → 401 ✓
Offen für η-1b Live-Smoke:
- mana-media-Service hochfahren (lokal :3015 oder prod)
MANA_SERVICE_KEYin.envsetzen- Echter Foto-Upload mit Multipart-Request (HTTPie/curl/UI)
- EXIF-Strip im
display-Layer verifizieren — falls mana-media's WebP-Transform EXIF behält, müssen wir Pre-Strip serverseitig einbauen (Sharp dann doch nötig). - Capture-Page um Foto-Picker erweitern (UI-Stück, η-1c-Anschluss).
Aktuelle Phase: η-1c — Capture-UI + Liste LIVE (2026-05-17)
✅ η-1c 2026-05-17 (Web-Schleife geschlossen)
- Dev-Auth-Stub in
apps/web/src/lib/auth.ts: localStorage-User-ID, HeaderX-User-Idan die API. Production-JWT-Pfad folgt η-5. - API-Client in
apps/web/src/lib/api.ts: type-safe Fetch-Wrapper mit Auth-Header.api.createObservation()+api.listMyObservations(). (app)/+layout.svelteAuth-Gate mit User-ID-Eingabe und Header-Navigation (Beobachten / Meine).(app)/capture/+page.svelteForm mit:- Wissenschaftlicher Name (Pflicht) + volkstümlicher Name
- Browser-Geolocation-Button (high-accuracy)
- State-Picker (gesund/blühend/fruchtend/geschädigt/welkend/abgestorben/ruhend)
- Notes
- Submit → POST /api/v1/observations (visibility=private)
- Erfolgs-Redirect zu /my/observations#created-{id}
(app)/my/observations/+page.sveltemit Visibility-Badge, Species-Anzeige (kursiv), Zustand-Label, GPS-Koord (Owner sieht exakt), Datum, Sensitive-Species-Hinweis.- Svelte 5 Runes-Modus in
svelte.config.js(compilerOptions.runes = true). - Smoke 2026-05-17:
pnpm check→ 0 errors / 0 warnings (250 Files).
End-to-End-Live-Pfad (manueller Test, sobald Docker hochgefahren):
docker compose up -d # PostGIS :5449
cd apps/api && pnpm db:push # Schema → DB
HERBATRIUM_AUTH_DEV_STUB=true \
DATABASE_URL=postgresql://herbatrium:devpassword@localhost:5449/mana_herbatrium \
bun run --hot src/index.ts & # API :3101
cd ../web && pnpm dev # Web :3102
# → http://localhost:3102/capture, Dev-Login "dev-till" eingeben,
# Form ausfüllen, GPS holen, speichern. Eintrag in /my/observations.
η-1b (Foto-Pipeline + mana-media) noch offen — braucht Sharp + laufenden mana-media-Service. Wird separat angegangen, wenn der Capture-Pfad ohne Foto im Live-Smoke grün ist.
Aktuelle Phase: η-1a — DB-Layer + Auth + Observations LIVE (2026-05-17)
✅ η-1a 2026-05-17
- Drizzle-Schema mit 7 Tabellen + PostGIS-customType
geometry(Point, 4326):species(fail-closedis_sensitive=trueDefault + IUCN/FFH-Felder)specimen(3-Stufen-geo_visibilityDefaultregion_only, GIST-Index aufgeo_point)observation(Defaultvisibility='private',licensenullable,published_atnullable)photo(3-Layer-Storage-Keys: original/display/thumb)identification_proposal(Audit-Trail mana-llm + Pl@ntNet)photo_original_access_log(η-4-Hard-Gate)takedown_request(η-7-Pflicht)
- Drizzle-Migration 0000 generiert (7 Tabellen, 11 Indizes, 6 FKs)
→
apps/api/src/db/migrations/0000_salty_franklin_storm.sql - JWT-Middleware inline (
apps/api/src/middleware/auth.ts):- Bearer-JWT gegen mana-auth/api/auth/jwks (createRemoteJWKSet-Cache)
HERBATRIUM_AUTH_DEV_STUB=trueopt-in für X-User-Id-FallbackrequireTier('beta')für Create-Endpoints
- Routes:
POST /api/v1/observations— fail-closed species-Anlage, Specimen-Auto-Anlage (η-3 macht PostGIS-Match), Defaultvisibility=privateGET /api/v1/observations/my— Owner-Filter inkl. private/readyzaktiviert mit DB-Ping (503 wenn DATABASE_URL fehlt)
- Verifikation 2026-05-17:
pnpm type-check✓ (kein Fehler)bun src/index.tsstartet/healthz200,/readyz503 ohne DB (klare Reason)/api/v1/observations/myohne Auth → 401 mit DetailX-User-Idohne dev-stub-Flag → 401 (fail-secure bestätigt)
Offen für η-1b/c: Foto-Upload-Pipeline (3-Layer-Resize mit Sharp, mana-media-Integration), Capture-UI, Observation+Photo-Bundle-Endpoint, DB-Live-Smoke (braucht Docker hochgefahren).
Aktuelle Phase: η-0 — Repo-Skelett LIVE (2026-05-17)
✅ Erledigt 2026-05-17
Skelett angelegt + verifiziert:
- Repo-Struktur
Code/herbatrium/(29 Files) - Workspace-Root (package.json, pnpm-workspace, turbo, tsconfig.base, Prettier, .gitignore, .npmrc, .env.example)
app-manifest.jsonmit den 4 neuen Share-Types (mana/photo,mana/geo-point,mana/plant-observation,mana/plant-specimen) + 3 Tools mit Beschreibung → validiert gegen@mana/shared-share-protocol@0.4.0(2 shares, 2 accepts, 3 tools, search: yes)docker-compose.ymlmitpostgis/postgis:16-3.4auf Port 5449, PostGIS-Extension viainit-postgis.sqlapps/api/Hono+Bun mit/healthz,/readyz,/healthz/details,/.well-known/mana-app.json→ alle 4 Smoke-Endpoints grün (Bun-Runtime, Port 3101)apps/web/SvelteKit 2 + Svelte 5 + Tailwind v4 + Herbarium-Theme, Landing-Page mit Phasen-Plan-Anzeige →pnpm check: 0 errors / 0 warnings (242 Files)- CLAUDE.md, README.md, dieser STATUS.md
- CI-Workflow
.github/workflows/ci.yml
Live deployed:
- Forgejo-Repo: https://git.mana.how/till/herbatrium (public,
default-branch
main, Initial-Commit gepusht 2026-05-17) - Verdaccio:
@mana/shared-share-protocol@0.4.0published aufnpm.mana.howmit den 4 neuen Share-Types (Underscore→Dash- Rename: Manifest-Validator erzwingt Kebab-Case nach^mana/[a-z][a-z0-9-]+$) - mana/-Repo: Commit
8b0dc34mit Share-Protocol-Erweiterung + Playbook + ECOSYSTEM/PORTS-Updates, gepusht zumain
Cross-Repo (mana/-Repo)
@mana/shared-share-protocol@0.4.0(von 0.2.0 → 0.3.0 → 0.4.0 nach Kebab-Case-Korrektur)- 4 neue Share-Types in
packages/shared-share-protocol/src/types/:photo.ts,geo-point.ts,plant-observation.ts,plant-specimen.tsmit Zod-Schemas + JSON-Schema-Generierung - 66 Tests grün, type-check grün, 19 JSON-Schemas generiert (
mealnachträglich in den Generator nachgezogen, war Lücke)
Verifikation η-0 (live ausgeführt 2026-05-17)
pnpm install # 150 packages, gegen npm.mana.how
pnpm validate-manifest # → ✓ 2 shares, 2 accepts, 3 tools, search: yes
cd apps/api && bun run src/index.ts & # listening on :3101
curl http://localhost:3101/healthz # → {"status":"ok","app":"herbatrium"}
curl http://localhost:3101/readyz # → {"status":"ready","db":"not-wired-yet"}
curl http://localhost:3101/healthz/details # → {"app":"herbatrium","version":"0.0.1","phase":"η-0"}
curl http://localhost:3101/.well-known/mana-app.json # → vollständiges Manifest
cd apps/web && pnpm check # → 0 errors / 0 warnings (242 Files)
Docker compose nicht verifiziert — Docker Desktop lief beim Smoke
nicht. PostGIS-Image wird beim ersten docker compose up -d gezogen,
DB ist in η-0 ohnehin nicht verdrahtet (Drizzle-Schema folgt η-1).
Nächste Schritte
Vor η-1
- Pl@ntNet-Citizen-Science-Account anlegen (Antrag-Vorlage in Playbook §„Operative Helfer").
- Pl@ntNet-DPA bei AGPV anfordern (E-Mail-Template in Playbook).
- Compliance-Re-Audit gegen v0.2-Plan (optional vor η-1, Pflicht vor η-4 Public-Gang).
- mana-auth App-Registrierung für
herbatrium(sso-origins.ts + apps-Tabelle + Service-Key — wenn η-1 das erste Mal Login braucht).
η-1 — Solo-Upload (geplant)
- PostGIS-Schema mit allen Tabellen aus dem Playbook (species, specimen, observation, photo, identification_proposal, photo_original_access_log, takedown_request)
- 3-Layer-Foto-Upload gegen mana-media
- POST/GET CRUD für Observations
- JWT-Middleware (JWKS-Cache aus mana-auth)
- UI:
/capturemit File-Input + Browser-Geolocation,/my/observations - Keine Klassifikation, keine Karte, keine Public-Sicht.
Architektur-Hard-Gates (Live-Voraussetzungen)
| Gate | Phase | Status |
|---|---|---|
photo_original_access_log läuft |
η-4 | offen |
is_sensitive-Worker fail-closed live |
η-4 | offen |
| Lizenz-Picker beim Publish | η-4 | offen |
| Pl@ntNet sendet nur display-Layer (Netz-Trace) | η-2 | offen |
Take-Down-UI + Auto-is_sensitive |
η-7 | offen |
legal/+page.svelte-AGB |
η-7 | offen |
| Pl@ntNet-DPA unterschrieben | vor erstem Live-Identify-Call | offen |
| Compliance-Re-Audit grün | vor η-0-Live-Deploy | offen |
Stand-Schema-Version
@mana/shared-share-protocol: 0.4.0 live auf npm.mana.how mit 4 neuen Types (mana/photo,mana/geo-point,mana/plant-observation,mana/plant-specimen). Kebab-Case-Naming wegen Manifest-Validator- Regex^mana/[a-z][a-z0-9-]+$.- Manifest-
schema_version:0.1(kein Bump nötig, neue Types sind additiv-rückwärtskompatibel)