Files
HiveGrid-Wireframes/hivegrid-cloud/AGENTS.md
T

333 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HiveGrid 2.0 — Agent-Regeln
Zentrale Regel-Datei für AI-Agents (Coding-Agents, ToolController-Worker), die an HiveGrid arbeiten.
**Letzte Aktualisierung:** 2026-06-19
**Architektur-Quelle:** Party-Mode-Diskussion mit Sally/Winston/Amelia, dokumentiert in `docs/milestones/README.md`.
## Arbeitsmodus & Wo-finde-ich-was (Stand 16.06.)
**Team — 2 Devs bauen PARALLEL** (+ David = Screens/Subframe, Dejan = Website, Haris = Vertrieb):
- **Raffael** — kritische Kern-Kette, SEQUENZIELL: P1 → P2 → P5 → P6 → P7 → P8 → P9 → P11 → P12 → P13.
- **Akeshan** — unabhängige Trabanten parallel dazu: Infra, P3, P4, P17, P19, dann (nach P5) P14, P15, P16, P20, P21.
- **Sync-Punkt:** P14/P15/P16 brauchen Raffaels P5 (Apps-Engine). **Parallel-Regel (AD-21):** jedes Modul bekommt eine eigene `prisma/schema/<modul>.prisma`; auf dem Feature-Branch `prisma db push`, **Migration erst beim Merge** — sonst Konflikte im Schema/in der Migrations-Historie. `core.prisma`/`accounts.prisma` nur für Relationen zu Tenant/User anfassen. Siehe §„Prisma-Schema & Migrationen".
**GSD = nur Infrastruktur, NICHT die Slash-Commands.** Wir nutzen die `.planning/`-Struktur + Codebase-Konventionen, arbeiten aber **ticket-getrieben** — KEINE `/gsd-plan-phase` / `/gsd-execute-phase`-Befehle.
**Ein Ticket bauen:** Du bekommst eine Ticket-ID → lies `.planning/ticket-docs/<ID>.md` (self-contained Bau-Spec: Was/Screens/DoD/Technische-Bausteine/Quellen/Abhängigkeiten) **und** diese AGENTS.md. Voller Einstiegs-Prompt + Designer-first-Regel (UI wartet auf „Davids Delta" im Screen-Ticket): **`.planning/ticket-docs/README.md`**. Vor dem Bauen die `blocked-by` aus der Doc prüfen. **Strikt im Ticket-/Ticket-Doc-Scope bleiben, Verbesserungen nur explizit ausgewiesen vorschlagen, Fortschritt als % der DoD melden** → siehe §„Ticket-Treue, Verbesserungs-Vorschläge & Fortschritt".
**Wo finde ich was:**
| Was | Wo |
|---|---|
| **Bau-Spec je Ticket (EINSTIEG)** | `.planning/ticket-docs/<ID>.md` (157 Tickets, je 1 verlinkte Datei) |
| Datenmodell — LOCKED, **Vorrang bei P6–P9** | `.planning/ENTITY-MODEL.md` (+ `entity-model.dbml`) |
| Phasen v1/v2 + Ziele/SC | `.planning/ROADMAP.md` · `.planning/ROADMAP-V2.md` |
| Phase → Milestone-Mapping | `.planning/PHASE-SOURCES.md` |
| Reihenfolge (Ticket an Start-Tag) | `.planning/SPRINT-DAYPLAN.md` |
| Requirements je Modul | `.planning/REQUIREMENTS.md` |
| Screen-Briefs (Designer) | `.planning/designer-briefs/PXX.md` |
| Architektur-Entscheidungen | `docs/ARCHITECTURE-DECISIONS.md` (u.a. AD-1 RLS · AD-17 PWA-only · AD-20 Projekt-zentrisch · AD-21 Prisma) |
| Live-Tickets/Status/Cycles | Plane: `project.tool-ctrl.de`, Projekt HiveGrid |
**Pivot-Vorrang:** Bei Widerspruch zwischen `ENTITY-MODEL.md` und den `_bmad-output/specs/M0x` (teils pre-pivot) gilt **immer das ENTITY-MODEL** (AD-20: ein `Projekt`-Kern, Pipelines=Views, „Deal"/„Auftrag"=Typen, Lead→Projekt, kein Won-Handoff). Die betroffenen Specs tragen oben einen 🔴 Pivot-Block.
## Plane-Tracking (Ticket-System — gleicher Standard für beide Devs)
**Wo:** self-hosted Plane `https://project.tool-ctrl.de`, Workspace `toolctrl`, **Projekt HiveGrid** = `35d0d968-8ab3-40cc-97b8-b81d917dc5df` (NICHT PowerLinked!). REST: `{BASE}/api/v1/workspaces/{WORKSPACE}/projects/{PROJECT}/…`, Auth-Header `X-API-Key`.
**Zugang — kanonisch über Environment-Variablen (identische Namen bei Raffael + Akeshan):**
`PLANE_API_KEY` (persönlich je Dev — **nie ins Repo/Git**) · `PLANE_BASE_URL=https://project.tool-ctrl.de` · `PLANE_WORKSPACE=toolctrl` · `PLANE_PROJECT=35d0d968-…`.
Fallback, wenn Env leer: dieselben 4 Keys aus `~/.cli-tokens/plane` (Zeilen `KEY=VALUE`).
**Wer wird imitiert? (vor jeder Schreibaktion prüfen):** Der `PLANE_API_KEY` ist persönlich → Kommentare/Assignee erscheinen unter *diesem* User. Identität verlässlich über `GET {BASE}/api/v1/users/me/` (liefert den User des Keys). **Nicht** an der git-Mail festmachen (Maschine kann `node1@…` sein, Plane-User aber `raffael@…`). Ergebnis muss zum Dev der Maschine passen → bei Mismatch STOP + melden.
**Member-IDs:** raffael `8e34a88c…` · akeshan `8bf43e86…` · david `6c1fc63f…` · dejan `a5883f5e…` · haris `48367c1f…`.
**Skripte:** `.planning/plane-sync/` — `dump_snapshot.py` (read-only Snapshot), Pflege idempotent via `scrub_plane.py`. Module=Tracks, Cycles=Wochen, blocked-by=JIT.
**Ticket abschließen (verbindliche Reihenfolge):** 1) `.planning/ticket-docs/<ID>.md`: Umsetzungs-Notiz mit **Datum + kurzem Proof** (Commits/Tests/Deploy-Run) + DoD abhaken + **%-Done-Stand** (siehe §„Ticket-Treue, Verbesserungs-Vorschläge & Fortschritt"). 2) Plane-Kommentar — **schlicht, 1. Person (= imitierter User), kein AI-Sprech, keine Em-Dashes (—)**. 3) Ticket auf **Done** — nur bei **100 % DoD**.
**Übersichten:** Done-Tickets ausblenden, nur offene Arbeit zeigen.
## Ticket-Änderungen dependency-aware (verbindlich)
Vor jeder maßgeblichen Ticket-Änderung (Start-/Zieldatum, Scope, Assignee/Owner) **oder** Code, der `blocks`-Tickets betrifft (Contracts aus `R-P1a`, `core.prisma`, Querschnitt-Services M001–M017):
1. **Beide Richtungen lesen:** `blocked-by` UND `blocks` — in Plane *und* in den `.planning/ticket-docs/`-Docs der Abhängigen.
2. **Abhängige nicht brechen:** Änderung so wählen, dass blockierte/folgende Tickets konsistent bleiben (z.B. Start-Datum nie hinter Ziel-/Vorgänger-Datum schieben; keine Contract-/Schema-Änderung ohne Nachzug der Konsumenten).
3. **Auswirkung dokumentieren** — in der eigenen Ticket-Doc *und* im betroffenen abhängigen Ticket (Muster: AD-23 → Eintrag in `AK-P4a`).
4. **„Fertig" heißt:** Abhängige bleiben grün (Tests/Typecheck) — proaktiv prüfen, nicht erst bei Bruch reagieren.
## Lokale Dev-Secrets (SOPS + age)
Geteilte Dev-`.env`-Werte liegen **verschlüsselt** in `secrets/dev.sops.env` (SOPS+age, beide Devs als Recipients). `pnpm env:pull` entschlüsselt nach `./.env`, `pnpm env:edit` ändert sie. Onboarding/Regeln: `secrets/README.md`. Der age-Private-Key bleibt lokal (`~/.config/sops/age/keys.txt`), **nie ins Repo**. Nur geteilte API-Keys liegen im Blob; lokale DB/Redis = Defaults aus `.env.example`.
## Die Boilerplate IST deine Codebase — kritisch
**Du baust HiveGrid NICHT from scratch.** Das `HiveGrid-Cloud/`-Repo enthält die EU-sterilisierte Boilerplate als Fundament. Das ist DEIN Code, du editierst, erweiterst und löschst ihn — du klonst nichts, du importierst nichts „von außen".
Konkret:
- `apps/web/`, `apps/api/`, `apps/worker/`, `packages/db/`, `packages/mail/`, `packages/queue/`, `packages/ui/`, alles weitere → das ist deine Arbeitsbasis
- `package.json` mit pnpm-Workspace, Prisma, BullMQ, MailerSend, shadcn/Radix → bestehender Stack, den du nutzt
- Du erweiterst, was fehlt (z. B. neue `packages/view-models/`, `packages/ui-app/`, neue Routes)
- Du löschst, was nicht zu HiveGrid gehört (Affiliate-Reste, Marketing-Demo-Inhalte aus dem ToolCTRL-Boilerplate, falls noch vorhanden)
- Du baust neue Module **in** dieser Codebase, nicht **neben** ihr
**Pre-Flight-Check beim Start (jedes Mal):**
1. `package.json` im Repo-Root existiert mit pnpm-Workspace-Konfig → wenn nein: STOP, melde „Boilerplate-Inhalt fehlt"
2. `apps/`, `packages/`, `prisma/` existieren → wenn nein: STOP
3. `pnpm typecheck && pnpm test` läuft grün → wenn nein: STOP
4. Keine US-Provider-Imports mehr (`grep -rE "@ai-sdk/openai[^-]|@ai-sdk/anthropic|remix-auth-google" --include="*.ts"` leer) → wenn nein: STOP, EU-Sterilisation nicht abgeschlossen. (Das `[^-]` lässt `@ai-sdk/openai-compatible` durch — das ist der EU-taugliche provider-agnostische SDK für Mistral/Scaleway, KEIN US-Import.)
Erst danach beginnst du mit dem ersten Milestone.
## Naming-Konvention (Stand 2026-06-04, siehe `docs/ARCHITECTURE-DECISIONS.md` AD-10)
**Wichtige Korrektur zu „benenne frei um":** Dieses Repo ist ein **git-Downstream** der Boilerplate
(`upstream` = `git@git.tool-ctrl.de:toolctrl-labs/boilerplate.git`). Boilerplate-Verbesserungen werden
via `git merge upstream/main` eingespielt. Deshalb wird **NICHT** umbenannt:
- **Geteilte Boilerplate-Pakete behalten `@toolctrl/*`** (z.B. `@toolctrl/foundation`,
`@toolctrl/view-models`, `@toolctrl/db`, `@toolctrl/search`) — mergebar mit upstream.
- **UI-Pakete heißen real `packages/ui-app`, `packages/ui-subframe`, `packages/ui-mock`** —
NICHT `packages/ui-hivegrid`. Wo Docs/PLANs „`packages/ui-hivegrid`" sagen, ist **`packages/ui-app`** gemeint.
- **`@hivegrid/*` nur für genuin NEUE HiveGrid-Pakete**, die upstream nie existieren (z.B. das
neue Activity-Paket). Diese kollidieren beim Merge nicht.
- Pfad-Korrekturen ggü. älteren Docs: Sidebar liegt in `apps/web/app/application/sidebar/`
(nicht `sidebar.config.ts`); Such-Index/`indexedEntities` gehört nach `packages/search/`
(nicht `packages/db/src/search/indexed-entities.ts`).
Die DoD-, Anti-Pattern- und Workflow-Pfade unten nennen ab hier bereits die realen Pfade (`packages/ui-app`, `apps/web/app/application/sidebar/`, `packages/search/`); nur Punkt oben behält `ui-hivegrid` als Mapping-Hinweis für ältere Docs/PLANs.
## Pflichtenheft als Quelle der Wahrheit
Verbindliches Pflichtenheft: `_bmad-output/pflichtenheft/` v1.1 (Stand 2026-05-09).
Master-Milestone-Liste in `docs/milestones/` ist die ausführungsreife Übersetzung in 176 Milestones. Pro Milestone ein Verzeichnis mit `PLAN.md`.
**Progress-Tracking:** `docs/PROGRESS-TRACKER.md` ist die Live-Übersicht. Du updatest sie nach JEDEM Status-Wechsel:
- Milestone-Start: Status auf `🔵 IN-ARBEIT`, Detail-Sektion gemäß Vorlage anlegen
- Slices abschließen: Slice-Liste abhaken mit Commit-Hashes
- Alle DoD-Punkte grün: Status auf `🟡 READY-TO-VERIFY`, warten auf User-Sign-Off
- User trägt `🟢 SIGNED-OFF` ein → erst DANN nächstes Milestone starten
Bei Konflikt zwischen Detail-Spec (`_bmad-output/specs/M*.md`) und Pflichtenheft gilt **das Pflichtenheft v1.1**.
## Definition of Done — verbindlich für JEDES Milestone
Ein Milestone gilt **nur dann als erledigt**, wenn ALLE folgenden Punkte wahr sind:
### A. Code-Layer-Trennung (PR-Block bei Verstoß)
1. Loader/Action in `app/routes/**` baut View-Model. KEIN Direkt-Import von `~/db` oder `@prisma/client` in `packages/ui-app/**`.
2. Domain-Logik in `app/domain/**` importiert NIE React oder UI.
3. View-Models in `packages/view-models/**` sind pure Funktionen ohne React/Prisma-Imports.
4. Subframe-Output in `packages/ui-subframe/**` wird NIE direkt von `app/routes/**` importiert — nur via Adapter in `packages/ui-app/src/adapters/`.
5. UI-Components rufen NIE `useLoaderData` / `useFetcher` auf — Routes injizieren VM und Action-Callbacks.
### B. Erreichbarkeit (Reachability — siehe `docs/INTEGRATION-CONTRACTS.md`)
6. **Sidebar-Eintrag** in `apps/web/app/application/sidebar/` (AppSidebar/AdminSidebar + SidebarItem) — oder Inline-Verlinkung aus bestehendem Modul, wenn das semantisch korrekt ist.
7. **Permission-Gate** auf dem Sidebar-Eintrag — Eintrag NUR sichtbar bei berechtigter Rolle.
8. **Route registriert** unter dem korrekten URL-Pattern (siehe INTEGRATION-CONTRACTS.md §3).
9. **Breadcrumbs** für Detail-Routes (siehe §4).
10. **i18n-Keys** für ALLE UI-Texte (Pflicht via M003-Build-Lint).
11. **Mobile-Tab-Bar-Eintrag**, falls das Modul mobile-relevant ist (§13).
12. **Customer-Portal-Konfiguration**, falls Endkunden das sehen sollen (§14, Default unsichtbar).
### C. Querschnitt-Wiring (Pflicht-Service-Aufrufe)
13. **Schreib-Aktion** ist auditiert (auto via M001-Prisma-Middleware oder manuell via `auditEvent`).
14. **Activity-Stream-Eintrag** auf relevanter Entität (Kontakt/Firma/Lead/Verkaufsfall/Auftrag) bei jeder relevanten Aktion via `logActivity`.
15. **Notifications** via M011-Service (`notify({...})`), niemals direkt Email/Push.
16. **Search-Index** registriert in `packages/search/` (indexed-entities-Registry — M00-Neubau), falls suchbar.
17. **Permission-Filter** via `withTenantScope()` + `withPermission()` (M004) — kein manueller Query-Filter im Modul-Code.
### D. Tests (PR-Block ohne)
18. **Unit-Tests** für View-Model-Builder (pure-function-test mit Fixture).
19. **Smoke-Test** in `e2e/smoke/<modul>.spec.ts` — Login → Route öffnen → Hauptaktion klicken → erwartetes Ergebnis sichtbar.
20. **Hot-Swap-Drill** (M017): Smoke-Tests müssen mit `packages/ui-mock` als Drop-in-Replacement laufen.
21. **Visual-Regression-Snapshot** (M016) für die Hauptansicht.
### E. Klartext-Hinweise (User-facing)
22. ALLE Fehler-Texte aus M002-Catalog (kein nackter HTTP-Code, keine kryptische Stack-Trace im UI).
23. **Empty-State** mit Klartext + nächster Aktion.
24. **Loading-State** mit Klartext (kein leerer Spinner).
25. **Permission-Denied** zeigt 404, nicht 403 (siehe Bauprinzip A.5.3).
### F. Dokumentation
26. PLAN.md des Milestones aktualisiert mit Lessons Learned, falls relevant.
27. **Rollback-Pfad dokumentiert** im Slice-Plan (siehe `docs/MID-STREAM-CHANGES.md`).
28. **Forward-Only-DB-Migration** mit Inverse-SQL als Kommentar im Migration-File.
---
## Drei Anti-Patterns — sofortiger PR-Block
1. `@prisma/client` oder `~/db` in `packages/ui-app/**`
2. Direktimport aus `packages/ui-subframe` in `app/routes/**`
3. `useLoaderData<typeof loader>()` innerhalb von `packages/ui-app/**`
## Daten-Fluss (verbindlich)
**Synchron im Loader.** Server bereitet das View-Model einmal auf, UI rendert. KEIN client-seitiges Re-Compute.
```ts
// app/routes/kontakte.$id.tsx
export async function loader({ params, context }: LoaderArgs) {
const entity = await context.db.kontakt.findUniqueOrThrow({ where: { id: params.id! } });
return json<KontaktDetailVM>(toKontaktDetailVM(entity, context.user));
}
export default function KontaktRoute() {
const vm = useLoaderData<typeof loader>();
return <KontaktDetailPage vm={vm} actions={{ onEdit, onArchive }} />;
}
```
## Prisma-Schema & Migrationen (verbindlich — AD-21)
Das Schema ist ein **Ordner** (`prisma/schema/`, Prisma 7), nicht eine Datei. Bei parallelen Branches gilt:
- **Ein Modul = eine eigene `prisma/schema/<modul>.prisma`** (viele existieren: `appsEngine`, `email`, `workflows`, `notifications`, `permissions`, `marketingLeads` …). Neue Modelle **NIE** in `core.prisma`/`accounts.prisma` — das ist der geteilte Kern (Tenant/User/Audit/File/ApiKey/Credential). Den nur für Relationen zu `Tenant`/`User` berühren, Rück-Relationsfelder minimal halten.
- **Feature-Branch: `prisma db push`** (kein Migrations-File). Die **eine** Migration pro Phase wird beim Merge auf `main` erzeugt — Forward-only + Inverse-SQL als Kommentar (DoD 28). Verhindert divergierende Timestamp-Historie bei parallelen Branches.
- Vor Merge: `prisma validate` + `prisma migrate status` grün. URLs liegen in `prisma.config.ts` (`DIRECT_URL` Migrate, `DATABASE_URL`/PgBouncer Runtime).
- **Migration muss aus dem Nichts reproduzierbar sein** (`infra/LESSONS.md §9`): CI testet `prisma migrate deploy` gegen eine **leere** DB — nur inkrementell auf Prod angewandte Migrations können bei Staging/DR/neuem Dev-Setup in falscher Reihenfolge brechen (gleicher Datums-Prefix sortiert lexikografisch, nicht nach Dependency → P3018). Order-abhängige DDL/DML in `DO $$ … IF [NOT] EXISTS … $$`-Blöcke guarden. Eine bereits auf Prod angewandte Migration **nie** umbenennen, ohne `_prisma_migrations.migration_name` nachzuziehen.
## EU-Stack (nicht verhandelbar — Bauprinzip A.12)
- **KEIN** `@ai-sdk/anthropic`, `@ai-sdk/openai`, `openai`
- **KEIN** `remix-auth-google`
- **KEIN** Apple-/Google-/Microsoft-Login (Phase 1)
- **JA** Mistral primär, Scaleway sekundär, AlephAlpha tertiär (M083, M084)
- **JA** Email+Passwort + Magic-Link + TOTP (M020, M021, M022)
- Hosting: Hetzner + Coolify (EU); Datenbank: in EU; Storage: Hetzner Object Storage / S3 (EU) — vgl. ADR-009 (löst den früheren Scaleway-Hosting-Plan ab)
## Querschnitt-Hülle nutzen, nicht nachbauen
| Concern | Querschnitt-Milestone | Service-Import |
|---|---|---|
| Audit-Log default | M001 | auto via Prisma-Middleware; manuell `auditEvent` |
| Klartext-Diagnostik | M002 | `errorCatalog`, `ErrorBoundary` |
| i18n | M003 | `t('<modul>.<bereich>.<key>')` |
| Tenant-Scope | M004 | `withTenantScope()`, `withPermission()` |
| Feature-Flag | M006 | `featureFlag('<key>', tenant)` |
| Idempotenz (BullMQ) | M007 | `withIdempotency()` |
| Apps-Engine-Adapter | M008-M010 | `defineAdapter()` |
| Notifications | M011 | `notify({...})` |
| View-Model-Layer | M013 | Type + Zod-Schema in `packages/view-models/src/<domain>/*.vm.ts` |
| Action-Callback-Contract | M014 | Standard-Shape `{ onX: (input) => Promise<...> | void }` |
| Subframe-Adapter | M015 | Adapter in `packages/ui-app/src/adapters/` |
| Visual-Regression | M016 | Playwright Component-Test mit Fixture-VM |
| Hot-Swap-Drill | M017 | `pnpm test:hot-swap` |
Wenn ein Modul Cross-Cutting-Concern selbst nachbaut → PR abgelehnt.
## Estate-Infrastruktur — verbindliche Regeln (Quelle: `infra/`)
HiveGrid ist **ein** Workload in einem geteilten Estate (neben mITapp + Marketing-Sites). **Quelle der Wahrheit ist das `infra/`-Repo** (eine Ebene über diesem Repo): `infra/README.md` (Bestand), `infra/STANDARDS.md` (der „so wird jeder SaaS aufgesetzt"-Vertrag), `infra/decisions/` (ADRs), `infra/LESSONS.md` (Footguns). Bei Widerspruch gewinnt `infra/STANDARDS.md`. Übersichts-Diagramm + Steckbriefe: `local/infra-diagrams/06-estate-infrastructure.{mmd,md}`.
**Goldene Regel: geteilte Estate-Services NUTZEN, nie pro-Produkt nachbauen** (analog zur Querschnitt-Hülle oben, nur eine Ebene tiefer — STANDARDS §1–§5/§13/§14). HiveGrid bekommt den Slug **`hivegrid`** (Sentry-Team, Meili-Index-Prefix, Cloudflare-Zone, Coolify-/Hetzner-Projekt `HiveGrid`).
| Concern | Zentral (mesh-only, wenn nicht anders genannt) | App-Env / Vertrag |
|---|---|---|
| Errors (STANDARDS §1) | Sentry `sentry.saas-ctrl.com` — 1 Team `hivegrid`, 1 Projekt je Codebase (`web`/`api`/`worker`) | `SENTRY_DSN_<APP>` · `SENTRY_ENVIRONMENT=staging\|production` · `SENTRY_RELEASE=$GIT_SHA`. Staging↔Prod = **Tag**, nicht eigenes Projekt. Browser-DSN als `VITE_SENTRY_*` **Build-Arg** (Coolify-Env ist nur Runtime). |
| FTS (STANDARDS §4, AD-5) | Meilisearch `10.42.0.4:7700` | `MEILI_URL=http://10.42.0.4:7700` · `MEILI_API_KEY=<hivegrid-scoped>`. Indizes **`hivegrid-*`**-prefixed, **nie** geteilt, **nie** Master-Key in der App. |
| Upload-AV (STANDARDS §13) | ClamAV `10.42.0.4:3310`, **INSTREAM** (kein Path-Scan, kein shared FS) | `CLAMD_HOST=10.42.0.4` · `CLAMD_PORT=3310`. Failure-Policy explizit: **fail-closed** für untrusted Uploads. >100 MB vorab ablehnen. |
| NSFW-Bilder (STANDARDS §14) | nsfw-api `10.42.0.4:3000`, `POST /classify` raw bytes | `NSFW_URL=http://10.42.0.4:3000`. Failure-Policy: **fail-open** (Gegenteil von AV). |
| Logs (STANDARDS §2) | Loki `10.42.0.4:3100` via Promtail (per Host) | App loggt **strukturiertes JSON nach stdout/stderr**, keine Secrets (Loki-Labels sind global queryable). |
| Metrics (STANDARDS §2/§3) | Prometheus+Grafana auf `control` (`grafana.saas-ctrl.com`) | App-`/metrics` optional, aber **bearer-token-geschützt**. Pflicht-Labels `project/environment/server/service`. |
| Admin-SSO (STANDARDS §5, AD-3) | Authentik `auth.tool-ctrl.de` (OIDC) | Nur **Operator/Admin**-Logins über Authentik. SaaS-Endkunden bleiben in der eigenen User-Tabelle. |
> Mesh-only-Services (`10.42.0.4`) haben **keine** Auth — Sicherheit = WireGuard-Mesh-Bind + UFW. Erreichbar nur als Mesh-Peer; **nie** einen Public-Port dafür fordern.
**Deploy & Repo (STANDARDS §6–§9, ADR-002/005/010):**
- **Coolify** ist das Deploy-Target (kein K8s/Terraform). Hierarchie: **Projekt = Produkt** (`HiveGrid`, TitleCase) · **Environment = Phase** (`production`/`staging`, lowercase) · **Resource = Service** (`hivegrid-web`/`-api`/`-worker`, kebab-case). Ein zentrales Coolify auf `control` steuert alle Hosts.
- **Build-once / deploy-via-pull:** CI (Gitea-Runner `toolctrl-ci`) baut **ein** Image, pusht in die **Gitea Package Registry** (`git.tool-ctrl.de/toolctrl-labs/<image>:<sha>` immutable + Alias-Tags); Coolify **pullt** nur — **kein** Build in Coolify. Rollback = altes `:<sha>` neu deployen.
- **CI triggert Coolify mit `POST /api/v1/deploy?uuid=…&force=true`** — NICHT `/restart` (das reuset das gecachte Image und shipped still alten Code, `infra/LESSONS.md §10`).
- Repo unter `toolctrl-labs/<name>` in **Gitea**, **private by default**, mit `Dockerfile` + `.gitea/workflows/`. Clone von Cloud-Hosts via **HTTPS+PAT** (Gitea-SSH:2222 ist von Hetzner unerreichbar, ADR-003).
- **DNS in Cloudflare** (SSL-Mode strict): A-Record `proxied=false` bis das LE-Cert steht, danach `true`. Mail: **OUT = Mailjet**, **IN = mailbox.org**.
**Secrets & Backups (STANDARDS §9/§10):**
- Secrets **nie** ins Repo / in Chat / in CI-Logs. Per-User-Tokens in `~/.cli-tokens/` (chmod 600); geteilte Dev-`.env` verschlüsselt via SOPS+age (`secrets/dev.sops.env`, `pnpm env:pull`).
- Ein **stateful** Service (Postgres/Redis-Persistenz/Index) darf **nicht** Prod-Traffic bekommen, bevor das Backup in `infra/runbooks/` dokumentiert ist (wohin, wie oft, Restore, letztes Restore-Drill-Datum).
Konkret tun (Host hinzufügen, Recovery, Secret-Rotation, neue Marketing-Site) → `infra/runbooks/*.md`.
## Subframe-Workflow
1. Designer baut Screen in Subframe
2. User pulled Subframe-Code → landet in `packages/ui-subframe/src/**`
3. AI-Coding-Agent integriert via Adapter-Layer in `packages/ui-app/src/adapters/**`
4. Routes importieren nur `packages/ui-app`, nie `ui-subframe` direkt
5. Visual-Regression-CI (M016) prüft auf unbeabsichtigte Diffs
6. Hot-Swap-Drill (M017) bestätigt: Tausch gegen `ui-mock` bricht nichts
## Anti-Liste respektieren — Bauprinzip A.9
Was nicht im Pflichtenheft v1.1 steht, ist nicht beauftragt. Vollständige Anti-Liste in `_bmad-output/pflichtenheft/02-vision.md` V.3.
Versucht ein Code-Vorschlag, einen Anti-Listen-Punkt umzusetzen → **PR abgelehnt**. Erweiterung nur über Change-Request-Workflow (`docs/CHANGE-REQUESTS.md`, M176).
## Ticket-Treue, Verbesserungs-Vorschläge & Fortschritt (verbindlich)
**1. Strikt am Ticket bauen — nichts erfinden.** Gebaut wird **ausschließlich**, was in der Ticket-Doc
`.planning/ticket-docs/<ID>.md` (Was / Screens / **DoD** / Technische-Bausteine) **und** im Pflichtenheft v1.1
steht. Verboten sind: erfundene Tasks, „während ich hier bin"-Scope, ungefragte Refactors und **vorgezogene
Arbeit aus Folge-Tickets/-Phasen** (z.B. flächiger Roll-out, wo das Ticket nur den Nachweis verlangt). Ist die
Ticket-Doc lückenhaft, mehrdeutig oder widersprüchlich → **Stop + Rückfrage** (siehe §„Bei Unklarheit"), kein
Auto-Ausbau. Alles, was über das Ticket hinausginge, läuft über den Change-Request-Workflow
(`docs/CHANGE-REQUESTS.md`, M176). Maßstab ist die **DoD der Ticket-Doc** — nicht das, was technisch „auch noch
ginge".
**2. Verbesserungen sind willkommen — aber IMMER explizit ausgewiesen, nie still gebaut.** Du **darfst** (am
Ende und gerne auch mittendrin) vorschlagen, was man besser/sicherer/sauberer machen könnte. Solche Hinweise
**müssen klar als Vorschlag außerhalb des Ticket-Scopes markiert** sein — Format: eine Zeile/Block mit Präfix
**„💡 Vorschlag (außerhalb Ticket-Scope): …"** + kurzer Begründung (Nutzen/Aufwand/Risiko). Erst nach
**explizitem OK** des Users wird daraus Arbeit (i.d.R. neues Ticket / CR). **Niemals** einen Vorschlag direkt in
Code/Commit ziehen, nur weil er sinnvoll wirkt — das ist ein PR-Block wie ein Anti-Listen-Verstoß.
**3. Fortschritt in % ausweisen — bis das Ticket auf 100 % steht.** Bei **jeder** Status- oder Abschluss-Meldung
zu einem Ticket den Stand als **% done** angeben, abgeleitet aus den DoD-Punkten der Ticket-Doc:
`% = abgehakte DoD-Punkte / DoD-Punkte gesamt`. Format z.B.: **„R-P2a: 6/7 DoD = ~86 % done — offen: <Punkt>"**.
DoD-Punkte, die **extern blockiert** sind (David-Screens, Partner-Input, Folge-Phase), als **`blockiert`**
kennzeichnen und vom **selbst machbaren** Rest trennen (z.B. „86 % done · 1 Punkt offen, davon 1 blockiert →
0 selbst machbar"). Den %-Stand zusätzlich in die **Umsetzungs-Notiz der Ticket-Doc** schreiben. Erst bei
**100 % (alle DoD grün, nichts mehr selbst machbar offen)** ist das Ticket abschlussreif → §„Ticket abschließen".
## Klartext-Diagnostik — Bauprinzip A.1
- Kein `throw new Error("401 Unauthorized")` — stattdessen Catalog-Fehler aus M002
- Kein hartkodierter UI-Text — stattdessen i18n-Key aus M003
- Jede Fehler-Meldung beschreibt: was passiert ist, was zu tun ist, welcher Knopf hilft
## Kommentar-Sprache (Code = Englisch, Dokus = Deutsch)
**Code-Kommentare werden auf Englisch geschrieben** — passe dich der umgebenden Datei an, und
der Code-Bestand ist praktisch durchgaengig englisch kommentiert (Messung 2026-06-19: nur ~1% der
Quelldateien mit Kommentaren enthalten Deutsch, meist Alt-Bestand). Das gilt fuer `*.ts`/`*.tsx`,
`*.prisma` sowie SQL/Migrations in `prisma/**`. Grund: dies ist ein git-Downstream der
`@toolctrl`-Boilerplate (siehe Naming-Konvention) — englische Kommentare bleiben mergebar und
konsistent mit upstream.
**Deutsch bleibt nur in den Projekt-Dokus:** `.planning/**` (inkl. `ticket-docs/`),
`docs/**`, `_bmad-output/**`, `local/**`-Planungsartefakte sowie die Umsetzungs-Notizen und
Plane-Kommentare. Diese sind bewusst deutsch und bleiben deutsch.
Faustregel: liegt die Datei im ausfuehrbaren Code (`apps/**`, `packages/**`, `prisma/**`) →
Kommentare Englisch. Liegt sie in einem Doku-/Planungs-Ordner → Sprache der Doku (meist Deutsch).
## RR7-Spezifika
- **`.server.ts`-Re-Export ist verboten** (User-Memory). Nutze stattdessen Component-Extraktion.
- Loader/Action sind die einzigen Pfade in `app/domain/**`. Keine Direkt-DB-Calls aus Components.
## Mid-Stream-Korrektur
Bei falschem Code: siehe `docs/MID-STREAM-CHANGES.md` für Eskalations-Stufen und Rollback-Pfad.
## Bei Unklarheit
Stop, Frage an User, kein Auto-Refactor. Dokumentiere offene Frage in der PR-Beschreibung.