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:
1 parent
11e4280a92
commit
7b1342fbb1
2 files changed
+22
-8
No files matched your search
@@ -121,7 +121,7 @@ Indizes: `status`, `entrance+status`, `room`, `foundAt`, `purgeAfter`.
|
||||
`ts dt*` · `workerId s(16)*` (oder `system`) · `kind s(32)*` (Liste §5) · `jobId s(36)` · `prospectId s(36)` · `text s(300)*` (deutscher Satz für Spur/Blase) · `link s(1024)` · `to s(16)` (Ziel-workerId bei Übergaben) · `tokensIn i` · `tokensOut i` · `costUsd f` · `metaJson s(4000)`. Index: `ts`, `workerId+ts`.
|
||||
|
||||
### 4.8 `workersDaily` – Tageszähler (`$id = "YYYY-MM-DD"` Berlin-Datum)
|
||||
`costUsd f*` · `mailsCold i*` · `mailsReply i*` · `searches i*` · `found i*` · `contacted i*` · `deals i*` · `countersJson s(4000)` (pro workerId: `{gelesen, gefunden, entwuerfe, gesendet, …}`) · `warned b*` · `stopped b*`.
|
||||
`costUsd f*` · `mailsCold i*` · `mailsReply i*` · `searches i*` · `found i*` · `contacted i*` · `deals i*` · `countersJson txt(16000)` (physisch TEXT 20000; läuft der Inhalt dennoch über, verdichtet `src/daily.js` und wirft nie – Endprüfung P1) (pro workerId: `{gelesen, gefunden, entwuerfe, gesendet, …}`) · `warned b*` · `stopped b*`.
|
||||
|
||||
### 4.9 `workersThreads` – Gesprächsfäden
|
||||
`prospectId s(36)*` · `channel e[mail,plattform]*` · `platform s(64)` · `platformUrl s(1024)` · `toEmail s(256)` · `subject s(300)` · `status e[offen,wartet_auf_kunde,wartet_auf_uns,ja,nein,abgeschlossen,gesperrt]*` · `lastInboundAt dt` · `lastOutboundAt dt` · `followupDueAt dt` · `followupSent b*` · `reminderDueAt dt` · `reminderSent b*` · `createdAt dt*`. Index: `status` (P1-E1).
|
||||
@@ -428,6 +428,17 @@ Stand: Abgleich der Teilpläne P1–P6 am 17.10.2026. Die Teilpläne haben ihre
|
||||
|
||||
---
|
||||
|
||||
|
||||
### 11.3 Verträge aus der P1-Endprüfung (verbindlich für P2–P6)
|
||||
- `jobs.finish/fail/touch` immer mit dem **`next`-Objekt** aufrufen (nicht mit der nackten Id); lange Läufe (Bau, P4) rufen `touch` höchstens alle 20 Minuten, sonst holt `reclaimStale` den Auftrag nach 30 Minuten zurück.
|
||||
- `suppress` liefert `{ created, ignored, unparsed }`; `ignored`/`unparsed` (Adresse nicht auswertbar) geht **immer** ins Pult (P3/P4), nie still verloren.
|
||||
- Ein Tick-Eintrag, der wegen Budget/Pause nichts tut, gibt `{ skipped: 'budget' | 'pause' }` zurück; der Tick setzt dann den Lauf-Marker zurück und meldet einmal `leerlauf` („Nacht ausgefallen: Budget“).
|
||||
- `claude.search`: `allowedDomains` nie leer (leer wirft) und nur **reine Hosts** (ohne Schema, Port, Pfad; Punycode) – P3/P6 übergeben Hosts.
|
||||
- Fehlertexte laufen zentral durch `schwaerzen` (`src/geheim.js`), bevor sie in Log, Ereignisse oder Bericht gehen.
|
||||
- Register-Listen (Freemail/Verzeichnisse/Baukästen) bleiben Aufzählungen; P6 ergänzt eine Laufzeit-Bremse: ein `dom`-Schlüssel, den mehr als 3 verschiedene Interessenten teilen würden, gilt als Plattform-Domain und wird nicht als harter Treffer gewertet (Hinweis ans Pult).
|
||||
- P2: Roster-Fixture ohne `**` in Maxʼ Rolle (byte-gleich mit `test/fixtures/workers-roster.json` aus dem Dienst); Polling-Takt bleibt unter dem nginx-Limit (20 r/s gemeinsam).
|
||||
- P4: `__bounce` und `__smtpStop` liegen in `countersJson` (16000 Zeichen; Verdichten behält `__smtpStop`).
|
||||
|
||||
## 12. Übernommene Ideen (Harvey)
|
||||
|
||||
Vom Inhaber freigegeben. Besitzer = Plan, der die Regel umsetzt; die anderen Pläne ziehen mit. Nicht verhandelbar bleiben die Spec-Entscheidungen (max. 3 Kaltmails/Tag, nur Max sendet, nur veröffentlichte Adressen, Sperrliste vor jeder Nachricht).
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user