Workers P1: Betriebsanleitung und Übersicht nach der Endprüfung (zwei Appwrite-Schlüssel, nginx-Zone, Erwartungstexte, countersJson 16000, Verträge für P2–P6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-18 00:47:57 +00:00
1 parent 11e4280a92
commit 7b1342fbb1
2 files changed
+22 -8

No files matched your search

@@ -764,7 +764,7 @@ Die Ergänzungen E1–E11 dieses Plans sind in die Übersicht übernommen: siehe
})
```
- [ ] **Step 2: Fehlschlag sehen.** `cd /home/webklar/apps/workers-dev && timeout 300 node --max-old-space-size=1024 --test test/daily.test.js` → FAIL `ERR_MODULE_NOT_FOUND` (`src/daily.js`).
- [ ] **Step 3: Implementieren.** Sperre: `WeakMap<db, Promise>`-Kette, Fehler brechen die Kette nicht. `updateDaily`: `get` → fehlt: `create(…, day)` mit Nullwerten (`countersJson: '{}'`, bei `ConflictError` neu lesen) → `mutate` auf dem Objekt → `costUsd` auf 6 Nachkommastellen runden → `JSON.stringify(counters)` > 4000 Zeichen wirft `Error('countersJson zu groß')` → `update` mit **nur** den Schema-Feldern (`costUsd, mailsCold, mailsReply, searches, found, contacted, deals, countersJson, warned, stopped`), nie mit `$…`-Feldern oder `counters`. `monthDocs`: `list('workersDaily', [Query.greaterThanEqual('$createdAt', berlinStartOfDay(<Monatsersten>)), Query.limit(40)])`. `markRun` schreibt `counters.__runs[kind] = key` in das Dokument `key`.
- [ ] **Step 3: Implementieren.** Sperre: `WeakMap<db, Promise>`-Kette, Fehler brechen die Kette nicht. `updateDaily`: `get` → fehlt: `create(…, day)` mit Nullwerten (`countersJson: '{}'`, bei `ConflictError` neu lesen) → `mutate` auf dem Objekt → `costUsd` auf 6 Nachkommastellen runden → `JSON.stringify(counters)` > 4000 Zeichen wirft `Error('countersJson zu groß')` *(Endprüfung P1: ersetzt – Feld ist jetzt `txt(16000)`, `daily.js` verdichtet bei Überlauf und wirft nie)* → `update` mit **nur** den Schema-Feldern (`costUsd, mailsCold, mailsReply, searches, found, contacted, deals, countersJson, warned, stopped`), nie mit `$…`-Feldern oder `counters`. `monthDocs`: `list('workersDaily', [Query.greaterThanEqual('$createdAt', berlinStartOfDay(<Monatsersten>)), Query.limit(40)])`. `markRun` schreibt `counters.__runs[kind] = key` in das Dokument `key`.
- [ ] **Step 4: Test laufen lassen.** Gleicher Befehl → PASS (6 Tests).
- [ ] **Step 5: Commit.** `git add src/daily.js test/daily.test.js && git commit -m "P1-T09: Tageszähler mit Sperre und Lauf-Markern" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"`
@@ -1893,8 +1893,11 @@ Reihenfolge einhalten. **[Claude]** = Schritt, den der ausführende Agent mit de
cp ticket.webklar.com/nginx/ticket.webklar.com.conf ticket.webklar.com/nginx/ticket.webklar.com.conf.bak-$(date +%F)-workers
```
*Prüfen:* `ls -l .env.bak-*-workers docker-compose.yml.bak-*-workers ticket.webklar.com/nginx/*.bak-*-workers` zeigt drei Dateien von heute.
3. **[Kenso] Appwrite-Schlüssel.** Appwrite-Konsole → Projekt `6a1058610003c5a13a05` → Integrationen → API-Schlüssel → Name `workers`, kein Ablauf, Bereiche `databases.read/write`, `collections.read/write`, `attributes.read/write`, `indexes.read/write`, `documents.read/write`. Dann:
3. **[Kenso] Appwrite-Schlüssel (zwei Stück, nie `databases.write`).** Appwrite-Konsole → Projekt `6a1058610003c5a13a05` → Integrationen → API-Schlüssel:
a) `workers` (Laufzeit, kein Ablauf), Bereiche **nur** `documents.read` und `documents.write`. Dann:
`read -rs -p 'Appwrite-Key workers: ' K; echo; printf 'WORKERS_APPWRITE_API_KEY=%s\n' "$K" >> /home/webklar/apps/.env; unset K`
b) `workers-setup` (nur für Schritt 7, Ablauf 1 Tag), Bereiche `databases.read`, `collections.read/write`, `attributes.read/write`, `indexes.read/write`. **Nicht** in `.env` schreiben; er wird in Schritt 7 per `read -rs` gelesen und danach in der Konsole gelöscht.
Grund: Appwrite-Bereiche gelten projektweit; `databases.write` könnte `woms-database` löschen, die Schreibsperre in `src/db.js` schützt nur den Codepfad, nicht den Schlüssel. [Endprüfung P1, Fund 16]
*Prüfen:* `command grep -c '^WORKERS_APPWRITE_API_KEY=.' /home/webklar/apps/.env` → `1`.
4. **[Kenso] Anthropic.** Console: Workspace „Hustler“ anlegen, dort das Ausgabenlimit (Workspace-Ausgabenlimit) auf **150 $/Monat** setzen [Kenso, 17.10.: max. 150 € im Monat; 150 $ ≈ 130 €, also sicher darunter, solange 1 € ≥ 1 $] und einen API-Schlüssel erzeugen (Spec §12). Dann `read -rs -p 'Anthropic-Key Hustler: ' K; echo; printf 'WORKERS_ANTHROPIC_API_KEY=%s\n' "$K" >> /home/webklar/apps/.env; unset K`. *Prüfen:* `command grep -c '^WORKERS_ANTHROPIC_API_KEY=.' /home/webklar/apps/.env` → `1`.
5. **[Kenso] Berichtskonto** (intern, kein Kaltkontakt; Passwort wird aus Serverpuls übernommen, ohne es zu zeigen):
@@ -1914,12 +1917,12 @@ Reihenfolge einhalten. **[Claude]** = Schritt, den der ausführende Agent mit de
7. **[Kenso] Sammlungen anlegen (erst prüfen).**
```
cd /home/webklar/apps/workers-dev
( export APPWRITE_API_KEY="$(sed -n 's/^WORKERS_APPWRITE_API_KEY=//p' /home/webklar/apps/.env | tail -n1)"; timeout 900 node --max-old-space-size=1024 scripts/setup-collections.mjs --check; echo "Exit $?" )
( read -rs -p 'Appwrite-Key workers-setup: ' APPWRITE_API_KEY; echo; export APPWRITE_API_KEY; timeout 900 node --max-old-space-size=1024 scripts/setup-collections.mjs --check; echo "Exit $?" )
```
*Erwartet:* je Sammlung Zeilen mit `FEHLT`, `Exit 1`. Dann derselbe Befehl **ohne** `--check` (Ende `Fertig.`; es sind rund 180 Attribute, Appwrite legt sie nacheinander an, das dauert einige Minuten; bei einem Abbruch durch `timeout` den Befehl einfach wiederholen, er zieht Fehlendes nach), danach wieder mit `--check` → `Alles vorhanden.`, `Exit 0`. Erscheint „Row width limit reached“, in `src/schema.js` die Größe des genannten Felds auf `TEXT_SIZE` setzen und neu laufen lassen (Skript ist idempotent).
*Erwartet:* je Sammlung Zeilen mit `FEHLT`, `Exit 1` (`--check` prüft auch den Status: `NICHT BEREIT (Status: processing|failed)` + `Exit 1` = später wiederholen bzw. bei `failed` das Feld prüfen; eine andere Option als `--check` bricht mit Exit 2 ab). Dann derselbe Befehl **ohne** `--check` (Ende `Fertig.`; es sind rund 180 Attribute, Appwrite legt sie nacheinander an, das dauert einige Minuten; bei einem Abbruch durch `timeout` den Befehl einfach wiederholen, er zieht Fehlendes nach), danach wieder mit `--check` → `Alles vorhanden.`, `Exit 0`. **Danach den Schlüssel `workers-setup` in der Appwrite-Konsole löschen**; Schritt 9 nimmt den Laufzeit-Schlüssel aus `.env`. Erscheint „Row width limit reached“, in `src/schema.js` die Größe des genannten Felds auf `TEXT_SIZE` setzen und neu laufen lassen (Skript ist idempotent).
8. **[Kenso] Modell-Prüfung (manuell, kein Test).**
`( ANTHROPIC_API_KEY="$(sed -n 's/^WORKERS_ANTHROPIC_API_KEY=//p' /home/webklar/apps/.env | tail -n1)" timeout 120 node --max-old-space-size=1024 scripts/check-modelle.mjs; echo "Exit $?" )`
*Erwartet:* Haiku-Zeile „web_search: unterstützt“, `Exit 0`. Meldet das Skript den Fallback, die Zeile `WORKERS_SEARCH_MODEL=claude-sonnet-5-5` an `/home/webklar/apps/.env` anhängen (die Compose-Datei reicht sie schon durch); `Exit 2` heißt „unklar“ (die Modell-Auskunft nennt Websuche vermutlich gar nicht): denselben Befehl mit `--probe` wiederholen (eine echte Probe-Suche, etwa 0,02 $) und dessen Ergebnis wie oben behandeln; `Exit 1`: Suche ist mit diesen Modellen nicht möglich, P3 nicht starten.
*Erwartet:* eine Zeile `claude-haiku-5-5: kann web_search` und die Schlusszeile `Haiku kann suchen: keine Änderung nötig.`, `Exit 0`. Meldet das Skript den Fallback, die Zeile `WORKERS_SEARCH_MODEL=claude-sonnet-5-5` an `/home/webklar/apps/.env` anhängen (die Compose-Datei reicht sie schon durch); `Exit 2` heißt „unklar“ (die Modell-Auskunft nennt Websuche vermutlich gar nicht): denselben Befehl mit `--probe` wiederholen (eine echte Probe-Suche, etwa 0,02 $) und dessen Ergebnis wie oben behandeln; `Exit 1`: Suche ist mit diesen Modellen nicht möglich, P3 nicht starten.
9. **[Kenso] Startbestand.** Erst `--dry-run`:
`( export APPWRITE_API_KEY="$(sed -n 's/^WORKERS_APPWRITE_API_KEY=//p' /home/webklar/apps/.env | tail -n1)"; timeout 900 node --max-old-space-size=1024 scripts/seed-register.mjs --dry-run )`
*Erwartet:* `TROCKENLAUF …`, `Alte Leads: 95 (blockiert: 2)`, `Kunden: <Zahl>` (soll zur WOMS-Kundenliste inkl. Abgesagt/Archiviert passen), `Schlüssel neu: N · schon vorhanden: M`. Stimmt das, derselbe Befehl ohne `--dry-run`; ein zweiter Lauf meldet `Schlüssel neu: 0`. *Prüfen:* Appwrite-Konsole → `workersKeys` hat N Dokumente. Meldet dir jemand eine Antwort/einen Widerspruch eines der 95 alten Lackierer, sag Claude Bescheid (Eintrag in die Sperrliste, Spec §12).
@@ -1933,8 +1936,8 @@ Reihenfolge einhalten. **[Claude]** = Schritt, den der ausführende Agent mit de
docker compose build workers
docker compose up -d --no-deps workers
```
*Prüfen:* `docker logs --tail 20 webklar-workers` enthält `workers listening on 8092` und keine Zeile mit dem Wort „fehlt“; `docker stats --no-stream webklar-workers` unter 768 MiB (erwartet < 150 MiB); `docker exec webklar-ticket-integrations wget -qO- http://webklar-workers:8092/api/workers/health` → `{"ok":true,"version":"0.1.0"}`; `docker exec webklar-workers ls /app/prompts` zeigt die 15 `.md`-Dateien (Bind-Mount des Checkouts, schreibgeschützt). Nie ein nacktes `docker compose up -d` (startet Gestopptes).
11. **[Kenso] nginx.** In `/home/webklar/apps/ticket.webklar.com/nginx/ticket.webklar.com.conf` den Inhalt von `deploy/nginx-workers.conf` direkt nach dem Serverpuls-Block (`location /api/serverpuls/ { … }`) einfügen, dann `docker exec webklar-ticket nginx -t && docker exec webklar-ticket nginx -s reload`. *Prüfen:* `curl -s https://ticket.webklar.com/api/workers/health` → `{"ok":true,"version":"0.1.0"}`; `curl -s -o /dev/null -w '%{http_code}\n' https://ticket.webklar.com/api/workers/state` → `401`.
*Prüfen:* `docker logs --tail 20 webklar-workers` enthält `workers listening on 8092` und **keine** Zeile, die mit `Abbruch:` beginnt oder `Konfiguration unvollständig` bzw. `Konfiguration ungültig` enthält (dann beendet sich der Dienst und Docker startet ihn neu: Schleife); `docker stats --no-stream webklar-workers` unter 768 MiB (erwartet < 150 MiB); `docker exec webklar-ticket-integrations wget -qO- http://webklar-workers:8092/api/workers/health` → `{"ok":true,"version":"0.1.0"}`; `docker exec webklar-workers ls /app/prompts` zeigt die 15 `.md`-Dateien (Bind-Mount des Checkouts, schreibgeschützt). Nie ein nacktes `docker compose up -d` (startet Gestopptes).
11. **[Kenso] nginx.** Backup der Datei anlegen. In `/home/webklar/apps/ticket.webklar.com/nginx/ticket.webklar.com.conf` **zuerst** den Inhalt von `deploy/nginx-workers-zone.conf` (eine Zeile `limit_req_zone …`) ganz an den Anfang, **vor** `server {`; ohne sie scheitert `nginx -t` mit `zero size shared memory zone "workers_api"`. Dann den Inhalt von `deploy/nginx-workers.conf` direkt nach dem Serverpuls-Block (`location /api/serverpuls/ { … }`) einfügen, dann `docker exec webklar-ticket nginx -t && docker exec webklar-ticket nginx -s reload`. *Prüfen:* `curl -s https://ticket.webklar.com/api/workers/health` → `{"ok":true,"version":"0.1.0"}`; `curl -s -o /dev/null -w '%{http_code}\n' https://ticket.webklar.com/api/workers/state` → `401`. Bei Dauerfeuer von außen antwortet nginx mit `429`, die API bei zu vielen gleichzeitigen Anmelde-Nachfragen mit `503`.
12. **[Kenso] Abnahme Bericht und Nachtplan.** Nach 06:30 Berlin holt der erste Tick den heutigen Bericht sofort nach: innerhalb von 2 Minuten nach dem Start kommt `Hustler-Nachtbericht TT.MM.JJJJ` bei `kenso.gri@gmail.com` an (auch Spam prüfen). Vor 06:30 oder zur Wiederholung:
`timeout 120 docker exec webklar-workers node scripts/jetzt.mjs bericht` und `timeout 120 docker exec webklar-workers node scripts/jetzt.mjs nachtplan` (nur solange `connectedRooms` leer ist). *Erwartet:* die Mail; `nachtplan` druckt `{"planned":[],"skipped":"keine_raeume"}` und erzeugt ein `leerlauf`-Ereignis von Otto in `workersEvents`. Meldet der Bericht `smtp_auth` im Ereignis „Morgenbericht nicht gesendet“: **nicht** wiederholen (der Dienst versucht es erst morgen wieder), Passwort prüfen – wiederholte falsche Logins sperren die Server-IP bei Hetzner.
13. **[Kenso] Abnahme API im Browser** (eingeloggt auf `https://ticket.webklar.com`, Entwicklerkonsole):