Add CREATE TABLE IF NOT EXISTS for rebreak.user_devices to the
hardware_id migration so fresh databases can migrate despite the
alphabetical ordering mismatch with 20260430_add_user_devices.
Also apply Prettier formatting to mdm.ts.
Add MdmEnrollmentStatus, UserDeviceMdmHealthRecord types and helpers:
- getLinkedUserDevices() to load iOS devices with NanoMDM UDIDs
- getMdmEnrollmentStatusesByUdids() for bulk NanoMDM lookups
- updateUserDeviceMdmHealth() to mirror status on UserDevice
Also fix PoolConfig option casing: queryTimeout -> query_timeout.
Adds mdm_id, mdm_enrolled, mdm_supervised, mdm_last_seen_at and an
index on mdm_id. Uses IF NOT EXISTS to stay idempotent because mdm_id
was added manually before the migration was created.
Add mirrored NanoMDM enrollment, supervision and last-seen fields
(mdm_enrolled, mdm_supervised, mdm_last_seen_at) to UserDevice model.
Migration will follow in a separate task.
LYRA_EVAL_CONCURRENCY (Batch statt Promise.all-30-parallel) und
LYRA_EVAL_TIMEOUT_MS als Env-Schalter, plus 429-Retry mit Backoff —
nötig für Live-Runs gegen Provider mit niedrigem TPM-Limit (Groq
on-demand 12k). Default-Verhalten unverändert; Prompts/Regeln/
System-Prompt nicht angefasst.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
community/posts.get.ts + social/profile/[userId].get.ts lieferten neben
nickname auch username an fremde Clients — username ist der Login-
Identifikator ({username}@rebreak.internal) und verletzt die Nickname-
Anonymitäts-Invariante (REQ-COMM-005 / R-DATA-07) + exponiert das halbe
Login-Credential-Paar. Frontend rendert das Feld nirgends (verifiziert);
totes Typ-Feld in stores/community.ts entfernt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eine global-blocklistete Casino-Mail (mpmgame.com) rutschte in die Inbox,
obwohl die Absender-Domain Layer-2-Hard-Block ist. Ursache: kein Erkennungs-
Fehler, sondern ein Scan-Gap. Der idle-daemon crash-loopte heute (Port-3016-
Race, ~49 Restarts) und machte nach jedem Reconnect/Cold-Start nur einen
INKREMENTELLEN Scan (lastUid). Mails, die waehrend einer Downtime ankamen,
fielen damit dauerhaft durch das last-seen-UID-Raster.
Fix in backend/imap-idle/index.mjs:
- Cold-Start (Prozess-/pm2-Restart): immer forceFullSweep
- Reconnect nach Error mit Downtime > 5min (RECOVERY_SWEEP_THRESHOLD_MS):
forceFullSweep beim naechsten erfolgreichen Connect
- Routinemaessiger IDLE-Renew (10min Close-Reopen): bleibt inkrementell
-> kein teurer Dauer-Sweep im Normalbetrieb
- triggerScan(conn, {forceFullSweep}) + Coalesce-State "full"/"incremental"
(ein full-Trigger wird nicht von einem spaeteren inkrementellen degradiert)
scan-internal.post.ts wertet body.forceFullSweep bereits korrekt aus.
Im Zweifel Full: eine global-blocklistete Domain darf nie durchrutschen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die inline-Regex /^(?!imapflow)/ griff nur gegen bare Specifier, nicht
gegen aufgelöste absolute Pfade (z.B. /node_modules/.pnpm/imapflow@x/…).
Nitro prüft inlineMatchers VOR externalMatchers — bei absoluten Pfaden
startete der String mit "/" → Lookahead ^(?!imapflow) schlug durch →
imapflow wurde gebundelt → imap-flow.mjs-Chunk → util.inherits-Crash
→ scan-internal 500 (Incident 2026-06-05, Regression in 1493752).
Fix: inline als Funktion mit expliziter blocklist (imapflow, expo-server-sdk,
@supabase/supabase-js). Deckt bare Specifier UND aufgelöste node_modules-Pfade.
Lokal verifiziert: imap-flow.mjs-Chunk weg, alle 3 Packages in server/package.json.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- mail/scan{,-internal}.post.ts + imap-idle: attach imap.on('error')
+ targeted uncaughtException/unhandledRejection guards so a
connection-level IMAP error (ECONNRESET / TLS disconnect) can no
longer propagate to a process-level uncaughtException and kill the
Nitro API (root cause of the staging 502 crash-loop)
- services/push.ts: lazy dynamic-import expo-server-sdk (singleton,
like voip-push.ts) to fix "Class extends value [object Module]"
(ESM/CJS undici interop) that broke DM push notifications;
+ nitro.config externals safety net
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Härtung der öffentlich downloadbaren Magic-Apps gegen Reverse Engineering
(Assessment: docs/specs/magic-re-hardening.md):
- Windows: protection.json per ACL auf SYSTEM+Admins (DNS-Token nicht mehr von
Standard-Usern lesbar) — setup.rs
- Mac: MacProfileInstaller.remove() + Debug-Supervision-Modi/Reset nur noch
#if DEBUG (kein Removal-/Debug-Pfad im Release-Binary)
- Mac: staging-URL einmal als Konstante statt 4x Literal; interne Infra-Notizen
aus String-Literalen raus
- Backend: Rate-Limit (10/IP/min) auf /api/magic/pair/redeem
NUR Backend-Teil deployt via Push; Mac/Win brauchen Xcode-/Cargo-Release-Build
(zied) + Smoke-Tests vor Release. MagicAPIClient.swift trägt etwas vorbestehenden
WIP mit (gleiche Magic-Client-Domäne).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verwaiste AdGuard-Clients (magic_<deviceId> existiert, aber DB-Row fehlt nach
Crash zwischen clients/add und DB-Upsert) führten beim Re-Register zu 400 → 502.
Jetzt: bei 400 auf clients/update zurückfallen und den bestehenden Client auf
die frisch generierte clientId umbiegen. Behebt Magic-Register-502.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ElevenLabs-Account hat ein Zahlungsproblem → Legend-TTS bekäme 502.
Leite Legend bis zur Klärung auf Cartesia sonic-3 (unlimited), damit
Legend-User Sprachausgabe behalten. Revert-Config steht im Kommentar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Devices/Magic:
- Offline-Profil-Enroll deaktiviert (410) — Lock-PW würde im Klartext im
Download landen; stationärer Schutz läuft jetzt nur über Rebreak Magic
- Mac-DNS-Template: ProhibitDisablement (Filter nicht abschaltbar)
- Push "Neues Gerät verbunden" an mobile Geräte bei neuer Bindung
- Realtime auf user_devices → Settings aktualisiert Magic-Bindings live
- Geräte-Detail-Sheet (Tap auf Gerät): Status, verbunden-seit, Schutz-Donut
Hard-Lock (server-gehaltenes Removal-PW, User sieht es nie):
- magic_removal_password generiert/gespeichert + in Profil injiziert (Lazy-Backfill)
- Reveal NUR bei Account-Löschung (user/delete) + Kündigung (stripe webhook),
per Resend-Mail + in-Response
- Signing config-gated (inaktiv ohne Cert; Lock greift auch unsigniert)
Migrations: user_devices-Realtime-Publication + magic_removal_password-Spalten
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apex-only-Format (bare domain) blockte nur z.B. bet365.com, NICHT
www.bet365.com — Casino-Content + Google-Ad-Links liegen aber auf www.
Adblock-Syntax ||domain^ deckt Domain + alle Subdomains ab.
Betrifft alle DNS-Plattformen (Mac/Windows/DoH-Server).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Schliesst hans-muellers K1-Befund (Datenschutz-Audit): der Coach-Prompt
sendete Identifier + Art.9-nahe Daten an US-LLMs (Gemini/OpenAI/Anthropic).
- message.post.ts: Geburtsjahr/exaktes Alter -> Altersgruppe (Dekaden-Bucket);
Stadt komplett entfernt (Bundesland bleibt). Geschlecht/Familienstand/Beruf/
Nickname unveraendert (gewollte Personalisierung; Nickname = Pseudonym).
- lyraMemoryExtract.ts: Extraction-Prompt reduziert Dritt-Klarnamen auf Rolle
("Frau Maria" -> "seine Frau"), keine Orte/Arbeitgeber im Memory-Content.
- 08-datenschutz-audit: Payload-Audit-Platzhalter durch Vorher/Nachher-Tabelle
ersetzt, K1 erledigt, ZDR-Update (DPA/SCCs deemed-signed, TIA offen).
Pseudonymisierung zaehlt jetzt als zweite Schutzmassnahme neben ZDR fuer die TIA.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New 'erinnerung' topic for manual Lyra community posts that gently remind
users they can add optional, anonymous profile details. Wording stays
jargon-free (no 'DiGA'/'data'/'study'). Manual-only, not in the auto-cron
catalog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Domain/display-name adds now force a full re-scan so newly-added gambling
senders are caught immediately instead of waiting for the incremental UID
window. IMAP-idle NOOP tick lowered 2min -> 30s to close the Junk-folder gap
faster (Outlook drops straight into Junk, which IDLE does not watch).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The old streak was non-functional: streaks.current_days was always 0 (never
computed/incremented), and the profile page read me.streak (0) + account
created_at as the "since" date — showing "0 days protected since <signup>"
for everyone. This is the DiGA key metric, so it had to be rebuilt.
New model: optimistic protection-coverage based on actual VPN/MDM protection
state, never resets to 0.
- backend: append-only protection_state_log + migration; POST /api/protection/event
(ingestion, deduped) + GET /api/protection/coverage (read-time compute, no cron);
server-side cooldown_disable event on cooldown resolve. Generous >6h-off/day rule.
- frontend: report protection on/off transitions (initial + flips, deduped) from
useProtectionState; rewrote profile StreakSection → half-donut (protected vs
unprotected) + progress bar (current streak → personal record) + empty state.
- coverage starts fresh from deploy (no historical backfill — clean data for DiGA).
- spec: docs/specs/protection-coverage-streak.md (shared contract).
- old streaks/streak_events/profiles.streak left intact (coach/scores consumers).
Also adds go-to-market one-pagers under docs/marketing/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der inkrementelle Scan-Pfad rief imap.search({ uid: 'X:*' }) ohne das
zweite { uid: true }-Argument auf → ImapFlow sendet "SEARCH UID X:*"
statt "UID SEARCH UID X:*" → Server antwortet mit Sequence-Numbers.
Die nachfolgende fetchAll(..., { uid: true }) interpretiert diese als
UIDs → fetcht die falschen (alten) Mails → neu eingegangene Gambling-
Mail (höchste echte UID) wird nie klassifiziert/gelöscht (>15min Lag).
Auch Ursache des "Command Error. 10" Log-Spams (Mega-UID-Liste).
Fix: { uid: true } als zweites search()-Argument.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
imapflow.status() liefert uidValidity als BigInt. Der Code reichte den BigInt in
JSON.stringify (patchFolderScanState) → 'TypeError: Do not know how to serialize
a BigInt' → vom stummen connection-level catch verschluckt → weder
patchFolderScanState noch markFullSweepDone liefen je → folder_scan_state blieb
{} + last_full_sweep_at NULL → inkrementeller Scan aktivierte nie (immer Full-Sweep).
Fix:
- serverUidValidity: Number((status).uidValidity ?? 0) — BigInt → number vor JSON.
- Stumme catches (auth/lock/conn) loggen jetzt; Persist-Calls (patchFolderScanState
x2, markFullSweepDone) in eigene try/catch mit console.error — Diagnostik bleibt
drin für Post-Deploy-Verify.
Lokal verifiziert: Build EXIT 0, imapflow extern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der inline-Negative-Lookahead (/^(?!...)(?!imapflow)/) griff nur auf den nackten
Specifier, nicht auf aufgelöste node_modules-Pfade. Bei Module-Graph-Shifts
(Phase-2 Prisma-Felder) wurde imapflow doch gebundlet → CJS-inherits-Bruch
(util.inherits: superCtor.prototype undefined) → scan-internal 500 → Mail-Filtern
(USP) down (Incident 2026-06-05).
Fix: expliziter external-Eintrag mit Pfad-Regex /(^|node_modules/)imapflow(/|$)/
erzwingt imapflow robust extern. Lokal verifiziert: imapflow landet in
.output/server/node_modules + output-package.json, NICHT als chunk.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase-2-Rebuild reaktivierte den bekannten imapflow/node-apn util.inherits-Bundle-
Bruch → scan-internal warf 500 → Mail-Filtern (USP) down. Rollback von
scan-internal.post.ts + db/mail.ts auf den funktionierenden Stand (5b57bea).
Schema (folder_scan_state, last_full_sweep_at) + Migration BLEIBEN angewendet —
kein Prisma-Drift; die Spalten warten ungenutzt auf den gefixten Phase-2-Retry.
Root-Cause (warum der inkrementelle imap.status/search-Pfad das Bundle bricht)
muss vor erneutem Phase-2-Deploy in der nitro-Externalize-Config gelöst werden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend-Lag-Fix Phase 1 — entlastet die CPU-Dauerschleife im Mail-Stack:
- delete mail-scan-cron.ts: der 30-Min-Nitro-Cron scannte alle User parallel
(Promise.allSettled) und war redundant zum IMAP-IDLE-Daemon (Single Source
of Truth). Reine Dauerlast ohne Mehrwert.
- imap-idle: In-Flight-Guard (scanInFlight + coalescePending). triggerScan ist
jetzt re-entry-safe — pro Connection max. 1 aktiver + 1 pending Scan statt
bis zu 8 gestapelt pro 2-Min-NOOP-Tick. Gilt für NOOP + exists-Event.
- plan-features: Pro mailAgents 3->2 (+ Math.min-Hack in coach/message aufgeräumt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ring.post: log [ring] when triggered
- voip-push: log [voip-push] sent on success with env (prod/sandbox) + callId
- chat.ts sendDirectMessage: when attachmentType=='call' parse audio:<state>:<sec>
into proper preview (Verpasster Anruf, Anruf abgelehnt, Anruf (m:ss), \u2026)
so post-call push has body text instead of empty.
- callkit.startOutgoingCall: skip on Android (telecomManager opens dialer UI \u2014
wrong for in-app WebRTC; iOS-CallKit only for audio-session mgmt).
- voip-push: build both APNs Provider (production+sandbox) and try each per
token with memoization. Fixes BadDeviceToken on Xcode-Dev-Builds where the
token is Sandbox-only.
- stores/call: only call callkit.displayIncomingCall when app NOT in foreground
\u2014 in foreground the /call route handles ringing UI, otherwise double UI
(system banner + fullscreen).
- patch react-native-callkeep: New-Arch TurboModule compatibility (no overloads,
no Bundle params in @ReactMethod).
- pushTokenRegistration: more verbose [voip] diagnostics.
- backend: skip Expo alert push to iOS devices that already received VoIP push
(CallKit + banner = double ring)
- native: receiveIncoming no longer triggers InCallManager.startRingtone —
CallKit/ConnectionService play their own ring. Dedup if same callId
arrives twice (Realtime + VoIP-Push race).
SOS (urge.tsx) uses /api/coach/message as a stateless LLM proxy for game
comments, share drafts and the stream fallback — sending SOS_BOOT +
[INTERN:] prompts. The endpoint persisted the full messages array into
coachSession for pro/legend users, so those internal prompts and the raw
JSON replies leaked into the Coach chat history as visible bubbles.
- Reactivate the sosMode flag (already sent by all three SOS call-sites):
when set, the endpoint skips coachSession persistence, memory extraction
and feedback detection — pure LLM proxy, no shared state.
- Add a defensive filter on /api/coach/history that strips internal
messages (SOS_BOOT, [INTERN:], [SYSTEM-HINT], raw JSON / [[CHIPS]]
replies) so already-contaminated sessions self-heal on next load.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend (voice-call groundwork, no call engine yet):
- Profile.callsEnabled (Boolean default true) + migration
- canCall(caller,callee): mutual-follow AND callee.callsEnabled — server-side hard guard
- POST /api/me/calls-enabled (opt-out toggle), GET /api/chat/can-call/:userId
- expose callsEnabled in /api/auth/me
Frontend:
- "Allow calls" toggle in Profile privacy section (default on, optimistic+rollback)
- Me.callsEnabled + i18n DE/EN/FR/AR
Bundled DM UI work from this session:
- image lightbox is now a swipeable carousel over all shared images (+ counter)
- keyboard stays open after sending (input ref refocus)
- voice notes: Instagram-style waveforms (own=white/mint, other=black/grey),
removed the blue progress dot; lazy-load expo-media-library with clean fallback
- expo-linear-gradient + expo-media-library deps
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>