feat: hive setup data model and API (Beute L1)
Backend Image / build-and-push (push) Successful in 5m5s
Frontend Image / build-and-push (push) Successful in 11m2s

Each colony can now hold its hive setup (Ist-Zustand) as validated
JSON with an append-only change log:

- catalog: hive systems and part types with dimensions, placement
  rules and state fields; Liebig-Zander 10 seeded with fixed ids
  (owner's equipment incl. Bausperre, Futterzarge, Nassenheider)
- pure domain module (operations, validator, derived facts) with
  shared JSON fixtures the frontend runs too
- API: catalog (hive-systems:manage), setup create/change/history/
  state at a day (colonies:read/update), 409 HIVE_CONFLICT with the
  current state, idempotent writes, HiveChanged event
- inspections can carry hive ops saved in the same transaction
- backup export/import of the new tables (backup wins per colony)
- docs: ARCHITECTURE section 13, Annahmen 95-100

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-15 21:23:23 +02:00
1 parent 8982d0796a
commit b48fb157c8
141 files changed
+15211 -17

No files matched your search

+45
View File
@@ -215,6 +215,12 @@ Fachliche Annahmen, getroffen weil das Lastenheft an der Stelle keine eindeutige
92. **Beim Bearbeiten einer Behandlung wählt die Oberfläche nie selbst eine Packung vor**: Nur das Anlegen schlägt die Packung mit dem frühesten Verfall vor; im Bearbeiten-Dialog steht nach einem Mittelwechsel „Ohne Packung“, bis der Nutzer wählt. Kosten bei falscher Annahme: Wer eine Abbuchung erwartet, muss die Packung ausdrücklich wählen. Abschnitt 12.11.
93. **Die Glocke zeigt für `COMPLIANCE` das Kurz-Etikett „Behörden“**: Die Badges der Glocke sind Einwort-Etiketten („Aufgabe“, „Vorschlag“); „Behörden & Prüfung“ würde die Zeile umbrechen und steht darum nur in `lib/notifications/labels.ts` und der Telegram-Kategorienauswahl. Kosten bei falscher Annahme: Zwei Bezeichnungen für dieselbe Kategorie. Abschnitt 12.11.
94. **Die Dosis ist auf drei Nachkommastellen begrenzt**: Das Eingabefeld lehnt mehr als drei Nachkommastellen mit „Höchstens 3 Nachkommastellen.“ ab (Packungsmengen rechnen auf Tausendstel, `roundToThousandths`). Kosten bei falscher Annahme: Eine feinere Dosis lässt sich nicht erfassen. Abschnitt 12.11.
95. **Die Liebig-Zander-Zarge misst außen 525 × 430 mm**: Der Wert stammt aus dem Innenmaß des Edelstahldeckels und steht im Katalog (Migration `20261017100100_hive_catalog_zander_liebig`, `test/fixtures/hive-ops/catalog.json`). Kosten bei falscher Annahme: Die 3D-Maße stimmen nicht; der Inhaber misst nach, der Katalogwert wird mit `hive-systems:manage` geändert. Abschnitt 13.
96. **Die Styropor-Einlage misst 465 × 365 × 20 mm**: Der Inhaber misst nach. Kosten bei falscher Annahme: Die Dämmung sitzt im Modell zu groß oder zu klein; Katalogwert ändern, kein Code. Abschnitt 13.
97. **Der Nassenheider Professional liegt in einer Leerzarge auf den Rähmchen der Zarge darunter**: Im Modell ist er ein Einsatz der Leerzarge (`EVAPORATOR` in einer `BOX` mit 0 Plätzen). Kosten bei falscher Annahme: Die Lage im Aufbau weicht vom Stock ab; der Inhaber bestätigt mit Foto, die Regel steht in `hive-validate.ts`. Abschnitt 13.
98. **Rähmchen-Plätze zählen von links, mit Blick auf das Flugloch**: Platz 1 ist links, Platz `frameSlots` rechts. Kosten bei falscher Annahme: Alle gespeicherten Platznummern sind gespiegelt; das lässt sich nur mit einer Datenmigration über alle Aufbauten beheben. Abschnitt 13.
99. **Den Aufbau ändert nur, wer `colonies:update` hat; den Katalog pflegt nur `hive-systems:manage` (ADMIN)**: STAFF hat `colonies:update` nicht und kann die Beute daher nicht ändern, auch nicht in der Durchsicht (403 `HIVE_FORBIDDEN`). `hive-systems:manage` kommt nur über die Katalog-Synchronisierung zur Rolle ADMIN, ohne Rechte-Migration. Kosten bei falscher Annahme: Mitarbeiter müssen den Aufbau über einen Administrator ändern lassen; der Inhaber vergibt `colonies:update` in der Rollenverwaltung, das ist kein Code. Abschnitt 13.
100. **Die Bausperre ist eine Einlage ohne Zustand; „Winter“ ist kein eigener Modus**: Die Bausperre (`FLOOR_INSERT`) hat kein Zustandsfeld, Einlegen und Entfernen sind die Operationen `addInsert` und `removePart`. Kosten bei falscher Annahme: Wer einen Winter-Modus erwartet, muss Einlagen einzeln setzen; ein Modus wäre eine spätere Erweiterung des Katalogs. Abschnitt 13.
## 7. Kubernetes
@@ -965,3 +971,42 @@ Die Oberfläche des Compliance-Blocks entstand in Task 14 bis 19: Prüfbereitsch
- **Ernte:** nur die Warnung „Im Sperrbezirk gelten Auflagen auch für Honig und Waben. Verfügung prüfen.“; sie speichert immer.
- **409.** `RESTRICTED_ZONE` und „Der Stand wurde gerade geändert. Bitte neu laden.“ erscheinen mit dem Servertext über dem Knopf, der Fokus springt auf die Meldung, die aktiven Sperren laden neu, und ein Knopf „Neu laden“ (Umsetzen, Vereinigen, Standkorrektur) lädt Volk und Stände neu und löscht die Meldung. Kein automatischer Neuversuch.
- **Service Worker.** Die Sperrbezirk-Pfade sind seit Task 14 nie zwischengespeichert; offline fehlt die frühe Sperre, der Server entscheidet.
## 13. Beute (3D-Aufbau)
Der Aufbau einer Beute (Boden, Zargen, Rähmchen, Einlagen, Dach) je Volk. Spec: `docs/superpowers/specs/2026-10-15-beute-3d-design.md`. Lieferung L1 ist das Backend ohne Oberfläche.
**Modul.** `src/hive` (`HiveModule`): `hive.service.ts` (Aufbau je Volk), `hive-catalog.service.ts` (Katalog), `hive-ops.ts` (`applyHiveOps`), `hive-validate.ts` (Validator), `hive-facts.ts` (abgeleitete Fakten), `hive-state-schema.ts` (Zustandsfelder der Teile). Hive-Fakten schreiben nie in `Colony` oder `Inspection`; `Colony.occupiedFrames` bleibt die Volksstärke.
**Speicherform.**
- `HiveSystem` (Maße, `template`) und `HivePartType` (Art, Maße, `frameSlots`, `stateSchema`, `active`) bilden den Katalog. Die Startdaten (Zander nach Liebig, 10 Rähmchen, 14 Teile) kommen per Migration `20261017100100_hive_catalog_zander_liebig` mit festen Ids; `test/fixtures/hive-ops/catalog.json` ist dieselbe Quelle für das Frontend, ein e2e vergleicht beide.
- `HiveSetup` hält den aktuellen Aufbau eines Volks als JSON (Stand und `version`), `HiveChange` das Protokoll: je Änderung `occurredOn` (Berliner Tag), `source`, die Operationen und `stateAfter`. Der Stand an einem Tag kommt aus dem letzten `stateAfter` bis zu diesem Tag. Nachträge sind möglich, aber nie vor dem `INIT`-Tag und nie in der Zukunft.
- Katalogteile werden nie gelöscht, nur `active: false`. Bestehende Aufbauten mit einer inaktiven Art bleiben gültig.
**Operationen.** `addPart`, `addInsert`, `removePart`, `movePart`, `setState`, `setFrame`, `setFrames`, `setLabel`; höchstens 100 pro Anfrage, höchstens 30 Teile im Stapel, Beschriftungen und Rähmchen-Namen höchstens 40 Zeichen. Die Regeln und die Gründe (`reason`-Codes wie `RULE_FLOOR_BOTTOM`, `STATE_OUT_OF_RANGE`, `UNKNOWN_FIELD`, `LIMIT_PARTS`) stehen im Kopf von `hive-validate.ts`; die Prüfreihenfolge entscheidet, welcher Grund zuerst gemeldet wird, und das Frontend muss sie übernehmen. Scheitert eine Operation am aktuellen Stand, wird die ganze Änderung abgelehnt (409 `HIVE_CONFLICT`).
**Endpunkte** (`/colonies/:id/hive`): `GET` (Stand und Fakten), `POST` (Aufbau anlegen), `POST changes` (Änderung mit `Idempotency-Key`: dieselbe Wiederholung liefert dieselbe Antwort ohne zweite Änderung), `GET changes` (Historie mit Cursor), `GET at` (Stand an einem Tag). Katalog: `GET /hive-systems`, `GET /hive-systems/:id/template` (Vorlage mit frischen Ids je Aufruf), `POST` und `PATCH /hive-systems`, `POST /hive-systems/:id/part-types`, `PATCH /hive-part-types/:id`.
**Sperrreihenfolge.** `Colony` FOR UPDATE, dann `HiveSetup` FOR UPDATE, dann `HivePartType` (`LOCKABLE_TABLES` in `src/prisma/row-lock.ts`: `HiveSetup` direkt nach `Colony`). Die Version steigt je Änderung um 1; zwei gleichzeitige Änderungen laufen nacheinander, die zweite sieht den Stand der ersten.
**Rechte.** Lesen `colonies:read`, Aufbau anlegen und ändern `colonies:update`, Katalog ändern `hive-systems:manage` (Gruppe „Beutensysteme“). `hive-systems:manage` kommt nur über die Katalog-Synchronisierung (`PermissionCatalogSyncService`) zur Rolle ADMIN, ohne Rechte-Migration. STAFF hat `colonies:update` nicht und kann die Beute nicht ändern (Annahme 99).
**Ereignis.** `HiveChanged` (Outbox, in derselben Transaktion wie die Änderung; Nutzlast `colonyId`, `version`, `source`, `inspectionId`). Ein nicht lebendes Volk ist schreibgeschützt: 409 wie `assertActive`.
**Durchsicht.** `POST /inspections` nimmt optional `hiveChange` (`ops`). Durchsicht und Beute-Änderung werden in einer Transaktion gespeichert und gemeinsam abgelehnt. Ohne `colonies:update` antwortet der Server 403 `HIVE_FORBIDDEN` und schreibt nichts. Der Tag der Änderung ist der Tag der Durchsicht (nicht in der Zukunft, nicht vor dem `INIT`-Tag); die Änderung hat `source` `INSPECTION` und verweist auf die Durchsicht.
**Backup.** Export und Import enthalten `HiveSystem`, `HivePartType`, `HiveSetup` und `HiveChange`. Katalogzeilen werden nach Id eingespielt (auch in eine frische Installation mit Startdaten). Je Volk gewinnt die Sicherung: Aufbau und Protokoll eines wiederhergestellten Volks ersetzen die vorhandenen. Nicht versiegelte Altdateien ohne diese Tabellen werden gemeldet und übersprungen (Abschnitt 8).
**Fehlercodes.**
| Code | Status | Bedeutung |
|---|---|---|
| `HIVE_CONFLICT` | 409 | Operation passt nicht zum aktuellen Stand; mit `opIndex`, `reason`, `current` (Version und Stand) |
| `HIVE_EXISTS` | 409 | Das Volk hat schon einen Aufbau |
| `HIVE_NOT_FOUND` | 404 | Das Volk hat noch keinen Aufbau |
| `HIVE_FORBIDDEN` | 403 | Durchsicht mit `hiveChange` ohne `colonies:update` |
| `TEMPLATE_NOT_SET` | 409 | Das System hat noch keine Vorlage |
| `SCHEMA_NOT_ADDITIVE` | 400 | Zustandsfelder dürfen nur angehängt werden |
| `TEMPLATE_INVALID` | 400 | Die Vorlage besteht den Validator nicht |
Formfehler (Form der Anfrage, zukünftiger Tag, Tag vor `INIT`, mehr als 100 Operationen) sind 400. Ein neues System beginnt mit leerer Vorlage; sie wird per `PATCH` gesetzt, sobald die Teile existieren.