Karten mit ≥4 Lapses werden im Stats-Endpoint als `leech_cards`
geliefert (mit Front-Snippet, Deck-Name, Lapses-Count, sortiert
desc, max 20). Stats-Page zeigt eine rote „Schwierige Karten"-
Sektion mit Link in den Card-Editor.
* apps/api/src/routes/me.ts: GROUP BY card → SUM(lapses) ≥ 4
Filter, frontSnippetFor()-Helper für alle 6 Card-Types
(basic, basic-reverse, cloze, image-occlusion, audio-front,
typing, multiple-choice). Cloze-Markup wird gestrippt damit
der Snippet UI-tauglich ist.
* apps/web Stats-Page: neue CardSurface mit Warning-Icon, scrollbare
Liste mit Front + Deck + Lapses-Badge, Empty-State.
* i18n in DE/EN/FR/IT/ES (5 Strings + plural-Form).
104/104 Tests grün (kein neuer Test — bestehende /me/stats-Tests
covern die Aggregations-Form), web check 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schließt die Ops-Lücke „kein versioniertes Schema-Tracking" aus
FEATURE_IDEAS.md.
* apps/api/src/db/migrations/0000_baseline.sql — Drizzle-generierte
Baseline-Migration, 355 Zeilen, 25 Tabellen + 5 Enums (cards- und
marketplace-Schema). Eingefrostet auf den Live-Stand 2026-05-12.
* apps/api/scripts/bootstrap-drizzle-tracking.ts — neues Script,
markiert die Baseline in einer bestehenden DB als „bereits
angewandt", ohne SQL erneut auszuführen. Verwendet sha256 wie
drizzle-orm/migrator (Hash 312d67ba1aeb…), idempotent.
* package.json: drizzle:migrate + drizzle:bootstrap-tracking
npm-scripts.
* docs/playbooks/DRIZZLE_MIGRATIONS_BOOTSTRAP.md — Hand-Over für
Prod (Bootstrap einmalig, dann normaler Workflow:
schema → generate → commit → migrate, kein push --force mehr).
Lokal verifiziert: 17/104 Tests grün, bootstrap idempotent,
drizzle-kit migrate erkennt die Baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folge-Hardening zu e1ddbf3, Cluster A2+A3 aus FEATURE_IDEAS.
* hooks.server.ts: restriktive CSP im Report-Only-Modus
(default-src 'self', script-src 'self', connect-src whitelist
auf cardecky-api/auth.mana.how/share/mcp). CARDS_CSP_ENFORCE=true
flippt auf den scharfen Header.
* docs/playbooks/SERVICE_KEY_ROTATION.md: 5-Schritt-Rotation für
CARDS_DSGVO_SERVICE_KEY bis Phase F-1 (mana-auth-managed Keys).
Forensik der Bypass-Periode 2026-05-08 → 2026-05-12 ist abgeschlossen:
nur 2 user_ids in der Cards-DB, beide legitim (tills95@gmail.com +
Smoke-Test-Sentinel c1a5, letztere via DSGVO-Endpoint aufgeräumt).
Kein ausgenutzter Bypass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Behebt live verifiziertes Auth-Bypass auf cardecky-api.mana.how
(X-User-Id → founder-Tier) und zieht im selben Patch das fehlende
Operations-/Compliance-Fundament nach.
* Auth-Middleware fail-secure: opt-in via CARDS_AUTH_DEV_STUB="true"
(war opt-out, Default true). Compose-Default flipped auf "false",
NODE_ENV="production" für cards-api ergänzt, env-Template
dokumentiert. vitest.config.ts + tests/setup.ts aktivieren den
Stub gezielt für Test-Suiten.
* Security-Headers: Hono secureHeaders() in apps/api,
SvelteKit hooks.server.ts mit X-Frame/X-Content-Type/Referrer/
HSTS in apps/web. CSP bewusst ausgelassen — eigener Sprint.
* CORS-localhost-Whitelist nur außerhalb Prod.
* Rate-Limiting (in-memory sliding window, dependency-frei) auf
share.receive 60/min/IP, media.upload 30/min/user,
decks.generate + decks.from-image 10/min/user, dsgvo.* 10/min/IP.
* Health-Endpoint mit echter DB- und MinIO-Probe; /healthz bleibt
Liveness, /healthz/details ist Readiness mit 503 bei Failure.
* DSGVO-Honesty: storage_ok + storage_error im Response (statt
schluckend console.warn), Account-UI zeigt Fehler-Toast.
* Audit-Log: strukturierte JSON-Zeile (kind: "audit") auf stdout
für /dsgvo/export, /dsgvo/delete, /me/export, /me/delete.
* Bug-Fix: duplizierte case "multiple-choice"-Clause in fsrs.ts.
Verifiziert: apps/api 17 Files / 104 Tests grün, apps/web check
0 errors. Deploy auf Mac Mini steht noch aus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routes (additiv unter /api/v1/marketplace/*):
- POST/GET /authors/me — eigenes Author-Profil anlegen/updaten/lesen
- GET /authors/:slug — public Profile-Lookup (banned-reason gestrippt)
- POST /decks — Deck-Init (Slug-Validation + Pflicht-Author-Profil +
CHECK auf paid + Pro-License)
- POST /decks/:slug/publish — Versions-Snapshot mit per-Karte
cardContentHash aus @cards/domain, per-Version-Hash, AI-Mod-Stub-Log,
atomarer latest_version_id-Bump in Drizzle-Transaction
- PATCH /decks/:slug — Metadaten-Update (Owner-Only)
- GET /decks/:slug — Public-Detail mit optional-auth-Middleware
Geport aus cards-decommission-base:services/cards-server/, mit
Greenfield-Anpassungen:
- Hashing über @cards/domain.cardContentHash (gemeinsame SoT
zwischen privatem cards.cards und marketplace.deck_cards), per-
Version-Hash als SHA-256 über sortierte Karten-Hashes mit Ord-Prefix
- AI-Moderation als R2-Stub (pass+rationale+model='stub'),
echte mana-llm-Anbindung in späterer Welle
- Auth-Middleware-Shape an Greenfield (userId/tier/authMode in
c.get(...) statt user-Object), optional-auth als Schwester für
anonymen Public-Read
- Hono-typing: outer Marketplace-Decks-Router ist Partial<AuthVars>
weil Public-GET kein JWT braucht; Auth-Subroute ist strict
Lese-Referenz:
- 3331 LOC altes cards-server-Code (routes, services, middleware,
lib) unter docs/marketplace/archive/code/ archiviert. Read-only,
nicht im Build-Path.
Verifikation:
- 16 neue Vitest-Tests (Slug + Version-Hash), 72 gesamt grün
- type-check 0 errors
- E2E-Smoke gegen lokale cards-api: Cardecky-Author + Deck
r2-stoische-ethik mit 3 Karten v1.0.0 (basic + basic + cloze),
per-Karten-Hashes geschrieben, ai_moderation_log-Row da, semver-409
+ paid-422-Errors verifiziert. Smoke-Daten danach aufgeräumt.
Verbleibend für R3+: Discovery (explore + search), Engagement (stars/
subscribe/fork), Smart-Merge mit FSRS-State-Erhalt; danach R4 PRs +
Card-Discussions, R5 Frontend-Routes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>