- MailJob/EnqueueInput: attachments als { documentId } (keine Bytes in Redis, nur mit tenantId)
- deliverMail: Anhänge mandantengebunden aus MailLog.tenantId laden, SHA-256 prüfen,
Größenlimit MAIL_MAX_ATTACHMENT_BYTES (Default 10 MB); Fehler -> failed ohne Versand
- SMTP-Provider reicht Anhänge an nodemailer durch; optionaler Provider für Tests
- Template craftvia_report_customer (de/en) ohne App-Link, eigene CUSTOMER_TEMPLATE_KEYS
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- docker-compose.coolify(.prebuilt).yml: Service craftvia-worker (Target worker, Chromium,
shm_size 1gb, gleiche Härtung), Craftvia-Variablen für app und worker.
- Dockerfile: worker-Stage mit HOME=/home/app (Chromium-Profil für non-root); lokaler
docker build der Targets runner und worker erfolgreich, PDF-Erzeugung im Image geprüft.
- .env.example/.env.prod.example/.env.coolify.example: alle Craftvia-Variablen inkl. RLS,
KI-Provider, PDF_CHROMIUM_PATH, OFFLINE_MAX_DAYS, API_RATE_LIMIT_*, AI_GENERATION_RETENTION_DAYS,
AI_MONTHLY_TOKEN_LIMIT.
- CI (.github, .gitea): Job gate mit Postgres (pgvector) und Redis als Service: migrate deploy,
seed, Passwort für craftvia_app, tsc, lint, build, npm run test.
- docs/craftvia/DEPLOY.md (aus den Certvia-Deploy-Docs abgeleitet): Architektur, Domains, Secrets,
Worker, Migrationen, RLS-Aktivierung, Backup/Restore, KI, Rate Limits, Aufbewahrung, Smoke,
Update/Rollback. build-and-push-images.sh baut craftvia-worker.
- Certvia-/ISMS-Dokumente aus docs/ nach docs/_certvia-archiv/ (mit README); Verweise in README.md
und Skript-Kommentaren angepasst.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Aufräumpunkt g (L1 offener Punkt 10): components/backoffice-frame.tsx – unter 1024 px ist die
Sidebar ein Drawer hinter einem Menü-Button (44 px, aria-expanded, schließt bei Navigation,
Hintergrund, Escape); ab 1024 px statisch wie bisher. Header kompakter auf schmalen Screens.
- Aufräumpunkt i (L7 offener Punkt 7): mobiler Berichtseditor speichert ungesicherte Eingaben
über useOfflineDraft (IndexedDB je Mandant/Nutzer). Ein Entwurf wird nur wiederhergestellt,
solange die Servertexte unverändert sind (sonst gewinnt der Server, z. B. nach Übernahme eines
Lotse-Vorschlags); nach Speichern/Absenden gelöscht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Aufräumpunkt h: writeAuditLog puffert innerhalb von inTransaction (AsyncLocalStorage) und
schreibt nach dem Commit; bei Rollback werden die Einträge verworfen, nur „denied" bleibt.
Verschachtelte Transaktionen nutzen den äußeren Puffer.
- Aufräumpunkt d: mergeCustomers läuft über inTransaction (sequenziell, geschützter Statuswechsel)
statt ctx.db.$transaction([...]) und ist damit auch bei RLS_ENFORCED=true atomar und in äußere
Transaktionen einbettbar.
- Aufräumpunkt e: AuditAction „read" (+ Label im Audit-Viewer de/en); Notdienst-Kunden- und
Objektsuche protokollieren als „read" statt „export".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Aufräumpunkt j: report.save_draft und report.submit mit Zod-Schemas (lib/sync/ops.ts) und
Registry-Einträgen → services/reports/sync-ops.ts. report.submit reicht baseVersion als
expectedWorkOrderVersion und aiReviewed an submitReport durch; Lotse-Entwürfe ohne Bestätigung →
rejected invalid. signature.capture bleibt unregistriert (Upload-Art für Unterschriftsbild fehlt).
- Aufräumpunkt b: „Übernehmen" in der Konfliktliste delegiert an den Sync-Dispatcher
(apply.ts#reapplyOperation, ohne baseVersion) statt des L2-Stubs; unterstützt
work_order.transition und report.submit. Hinweistext der Konfliktliste angepasst.
- Aufräumpunkt c: getFieldBundle liefert je Auftrag mySession (eigene aktive WorkSession); die
Offline-Ansicht leitet den Zeitstatus daraus ab (alte Bundles: Näherung über Auftragsstatus).
- scripts/test-betrieb-sync.ts (Bundle, clientId je Mandant, Berichts-Ops, Konflikt-Übernahme,
Mandant B, Monteur ohne Zuweisung); test-einsatz-sync.ts prüft „nicht verfügbare Op" jetzt mit
signature.capture, weil report.save_draft registriert ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 20260915090000_betrieb_client_id_per_tenant (Aufräumpunkt f, L8 offener Punkt 6): globale
Unique-Indizes auf client_id (material_usages, work_sessions, time_entries, activity_notes,
photos, voice_notes, reports, signatures) → @@unique([tenantId, clientId]). Offline-IDs sind nur
je Mandant eindeutig; ein Replay derselben ID in einem anderen Mandanten scheiterte bisher mit
internem Fehler. Keine neuen Tabellen.
- 20260915091000_betrieb_ai_token_limit (Aufräumpunkt k): tenant_settings.ai_monthly_token_limit
(NULL = Plattform-Vorgabe AI_MONTHLY_TOKEN_LIMIT, 0 = unbegrenzt). Keine neue Tabelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- src/lib/api/openapi.ts: statisch gepflegte Spezifikation aller v1-Routen inkl. Fehlerformat,
Pagination, Idempotenz (clientOpId/clientId), Konflikte, Rate Limits, Rechte je Operation.
- GET /api/v1/openapi.json liefert das Dokument (angemeldete Nutzer).
- docs/craftvia/API.md: Kurzdoku mit Endpunkt-Tabelle.
- scripts/test-betrieb-api.ts: jede Route nutzt requireApiContext/respond.ts, 401 ohne Sitzung
im einheitlichen Format, 403 bei fremdem Origin/Sec-Fetch-Site, Fehler-Mapping, Rate Limit je
Nutzer (Standard/Einsatz getrennt), OpenAPI deckt jede route.ts ab.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aufräumpunkt a: Die lane-lokalen API-Kontexte (imports/_context.ts, sync/api-context.ts,
reports/http.ts, work-orders/_http.ts mit moduleGuard) sind entfernt. Alle v1-Routen laufen über
requireApiContext (DB-autoritative Rechte, 401/403) und withApi/toErrorResponse (respond.ts):
- Fehlerformat überall { error: { code, message, details? } }; invalid und blocked → 422,
conflict → 409, payload_too_large → 413, rate_limited → 429 + Retry-After.
- Same-Origin-Prüfung in withApi für jede Mutation vor der Anmeldung (vorher fehlte sie bei
imports, reports und work-orders).
- Rate Limiting je Nutzer mit rate-limit.ts: api (API_RATE_LIMIT_PER_MINUTE, 300/min) und
apiField für sync/uploads/field (API_FIELD_RATE_LIMIT_PER_MINUTE, 1200/min).
- Clients angepasst: Import-Uploader liest das neue Fehlerformat, Upload/Outbox werten 422 als
endgültig ungültig (429 bleibt transient mit Backoff).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Liste und Detail mit fünf Prüfschritten (Kunde, Objekt, Auftrag, Bericht,
Abrechnung), Navigationseintrag, Dashboard-Kachel verlinkt auf die Prüfung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kunde suchen oder vorläufig anlegen, Einsatzort mit Ansprechpartner,
Grund mit optionaler Sprachnotiz, Beginn und Team; Start über die Sync-Op.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Aufträge: createWorkOrder/transitionWorkOrder/getCompletionBlockers aus L2
- Dokumente: storeFile aus L1; neu services/documents/read.ts (readStoredBytes,
readDocumentBytes mit Prüfsummenprüfung); L2-Upload nutzt zentrale Ablage
- Dubletten aus L1 (findDuplicateCustomers(ctx)); Objekt-Kandidaten als
imports/site-candidates.ts; Dateityp-Erkennung mobil als field/mime.ts
- Objekt-Historie mobil als Adapter auf L1 getSiteHistory, PDF über /api/v1/reports/:id/pdf
- L4-Upload-Idempotenz: fester Upload-Lineage nach storeFile, Race → Soft-Delete + Replay
- PDF-Worker-Kontext: document:write zum Ablegen des Berichts-PDF
- Import: doppeltes Work-Order-Audit entfernt; bestätigte Aufträge starten planned
- Dateilinks in Auftragsdetail auf /files/[documentId]
- proxy: /api/v1 ohne Session → 401 JSON statt Login-Redirect
- ARCHITEKTUR §2: Objekt-Historie für Feldrollen (freigegebene Einsätze aller Teams)
Gate: tsc, lint, build, 42/42 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bericht docs/craftvia/lanes/stammdaten.md (Umfang, Dateien, Tests, Smoke,
offene Punkte) sowie umbrechende Suchfelder der Kunden- und Objektliste bei
schmalen Viewports.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Konflikte gelöst: Header mit Suche (L2) und Glocke (L6), Audit-Labels vereinigt
(ohne doppeltes sync_operation), Navigation mit Benachrichtigungen und Auftragsvorlagen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BullMQ lehnt benutzerdefinierte Job-IDs mit ':' ab; dispatchJob fiel dadurch
mit Redis immer auf die Inline-Verarbeitung zurück (gemeldet von L4).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/craftvia/lanes/einsatz.md: Umfang, Routen, Dateien, Tests, Stubs und
Abhängigkeiten, bekannte Lücken, Gate- und Smoke-Ergebnis.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- /m in eigene Route-Group (field)/m verschoben (emergency-Platzhalter mit),
Zugriffsprüfungen aus (app)/layout.tsx nach server/app-access.ts extrahiert
und von Backoffice- und Mobile-Shell gemeinsam genutzt
- Mobile Shell mit Bottom-Nav (Heute · Aufträge · Notdienst · Sync · Profil)
und Online/Offline-Badge; Startseite rollenabhängig (Feldrollen → /m),
Login-Default-Redirect auf /
- Heute, Auftragsliste mit Tabs, Auftragsdetail mit einer Primäraktion je
Zustand, Unterseiten Fotos (Kamera, Kompression, Upload-Fortschritt),
Notizen + Sprachaufnahme, Material mit Stepper, Checkliste, Zeiten mit
Korrektur, Profil; Sync-Platzhalter für L7
- Client-Wrapper submitOp (lib/field/client-ops.ts), Upload mit Fortschritt,
Bildkompression, Formatierung; Texte in messages/{de,en}/field.json
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kernlogik, Mandantentrennung (Mandant B liest/ändert nichts von A) und
Rollen/Scope (Monteur ohne Zuweisung → not_found/forbidden).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Listen mit Suche/Filter/Paginierung, Popups für Anlage und Bearbeitung,
Kundendetail mit Tabs, Dublettenhinweis und Zusammenführen, Objektdetail mit
Kartenlink, Dokumenten-Tab und Historie, Teamverwaltung mit Mitgliedern,
Dokumentenübersicht. Texte in messages de/en, Audit-Label Ansprechpartner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
storeFile (Allowlist, Magic Bytes, Größenlimits, Dateinamen-Normalisierung,
SHA-256, Versionierung über lineageId), FileScanner mit optionalem ClamAV-Hook,
Sichtbarkeits-/Scope-Autorisierung, Upload-Route und Umbau der Download-Route
von files/[...key] auf files/[documentId].
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kunden (Nummernkreis, Ansprechpartner, vorläufig bestätigen, Zusammenführen mit
Bestätigung), Objekte inkl. Historie, Teams mit Mitgliedschaften, Dublettenlogik
(lib + Service), API-Kontext/Antwortformat unter src/server/api und die Endpunkte
/api/v1/customers, /api/v1/sites, /api/v1/sites/[id]/history.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/reports mit Filtern (zur Prüfung zuerst), /reports/[id] mit Aktionen, Versionen und PDF-Link;
mobile Komponenten für Bericht, Prüfung und Unterschrift inkl. Signature-Pad; Texte de/en.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Actions mit moduleGuard("reports"); POST daily-report/completion-report, POST approve,
GET pdf sowie Dateiauslieferung für im Bericht referenzierte Dokumente.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReportContent-Vertrag, Content-Builder mit Tagesfilter, Services für Tages-/Abschlussbericht,
Bearbeiten, Absenden, Freigabe, Zurückweisen, neue Version, Unterschrift und PDF-Erzeugung
(playwright-core, Worker-Processor, Dockerfile-Stage worker). Stubs für L2-Transition/Blocker
und Dokumenten-Store.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 Prüfungen: IP/User-Agent null außerhalb eines Requests, Anzeigename und
Reply-To des Mandanten, Plattform-Adresse bleibt, Header-Injection bereinigt,
Rückfall auf Plattform-Defaults für Plattform-Mails.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- /imports: Upload per Drag & Drop/Dateiauswahl mit Fortschritt, Liste mit Status, Neu verarbeiten
- /imports/[id]: Originaldokument + Prüfmaske (Kunde, Objekt, Ansprechpartner, Auftrag, Positionen),
unsichere Felder markiert, Kunden-/Objektentscheidung, Bestätigen/Verwerfen
- Datei-Route für die Vorschau, Server Actions (moduleGuard("imports"))
- API: POST /api/v1/work-orders/import, GET /api/v1/imports/[id], POST /api/v1/imports/[id]/confirm
- Texte messages/{de,en}/imports.json, Bericht docs/craftvia/lanes/import.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Migration audit_request_context: ip_address, user_agent an audit_logs (Spec §26)
- writeAuditLog/writePlatformAudit erfassen IP (X-Forwarded-For) und User-Agent
aus dem Request; außerhalb eines Requests (Worker/Skripte) null
- Audit-Viewer liest die neuen Spalten statt Heuristik aus before/after
- deliverMail nutzt Anzeigename und Reply-To aus TenantSettings (Spec §33.2);
Absenderadresse bleibt Plattform-Domain (SPF/DKIM), Header-Injection bereinigt
Gate: tsc, lint, build, 24/24 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die parallelen Lane-Worktrees liegen unter .claude/worktrees im Repo; ESLint und
tsc erfassten deren .next-Artefakte und brachen das Gate ab.
Gate nach Merge lane/benachrichtigungen: tsc, lint, build, 24/24 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umfang, Empfängerregeln, Verträge für andere Lanes (occurrenceId, approvalStage,
startedAt/endedAt), Dateien, Migration, Tests, Gate und Smoke, Fundament-Bedarf
(IP/User-Agent im Audit-Log, Absender/Reply-To je Mandant im Mail-Kern), bekannte Lücken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Glocke im Backoffice-Header (ungelesen-Zähler, letzte 10, alle gelesen), mobil
einbindbar über variant="mobile".
- /notifications mit Filter gelesen/ungelesen/Art, Öffnen markiert gelesen (nur
relative Links), Pagination.
- /account Abschnitt Benachrichtigungen: E-Mail-Opt-out je Typ, Notdienst Pflicht.
- /settings/email (tenant:manage): Absendername, Antwortadresse, Empfänger Notdienst
und Abrechnung; Validierung gegen Header-Injection, max. 20 Adressen, Audit.
- Actions unter actions/notifications mit moduleGuard + guard; Navigation ergänzt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- docker-compose.yml: explizite Build-Targets (runner/migrate) — Ursache für
"npx not found" bei garage-provision war das Default-Target (letzte Stage =
Garage-Image ohne Node); worker startet npm run worker:mail; Defaults craftvia
- Coolify-Compose: incident-inbound-worker und sync-policy-templates entfernt,
Rollen-/Bucket-/Image-Namen auf craftvia
- Dockerfile: seed/ und docs/wizard-uebergabe entfernt, messages/ im Runner
- npm: Name craftvia, Scripts test (scripts/run-tests.ts) und gate, ISMS-Pakete
entfernt (handlebars, marked, sanitize-html, @xyflow/react, @dagrejs/dagre,
html-to-image, imapflow, mailparser, exceljs, @dnd-kit/core)
- .env-Beispiele, launch.json (craftvia-dev), CI-Kommentare umbenannt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>