cards/docs/playbooks/MARKETPLACE_RESTORE.md
Till JS 9a7068dd19 Phase 12 R0+R1: Marketplace-Restore-Plan + Schema in marketplace-pgSchema
R0 (Doku):
- Archiv unter docs/marketplace/archive/ aus managarten-Tag
  cards-decommission-base: MARKETPLACE_PLAN (654 Z., Vollvision mit
  mana-credits-Flow, Anti-Patterns), COMPETITORS, GUIDELINES,
  cards-server_CLAUDE.
- docs/playbooks/MARKETPLACE_RESTORE.md mit Schema-Naming-Entscheidung
  (eigenes marketplace-pgSchema), Wellen R0-R6, Cardecky-Skill-
  Integration, Lizenz-Modell.
- CLAUDE.md Invariante 2: Strategie-B gilt nur für Study-/FSRS-/Sync-
  Schicht; Marketplace-Restore ist explizite Ausnahme.
- STATUS.md: Phase 12 R0+R1 durch.

R1 (Schema):
- 16 Tabellen + 5 Enums im neuen marketplace-pgSchema (authors,
  decks, deck_versions, deck_cards, tag_definitions, deck_tags,
  deck_stars, deck_subscriptions, deck_forks, deck_pull_requests,
  card_discussions, deck_reports, ai_moderation_log, deck_purchases,
  author_payouts, author_follows).
- drizzle.config.ts: schemaFilter ['cards', 'marketplace'].
- Greenfield cards-pgSchema unangetastet.
- DB-CHECK decks_price_requires_license verifiziert (paid Deck mit
  CC-BY wirft sauber ab).
- type-check + 56 API-Tests grün, drizzle-kit push idempotent.

Decks dormant (kein Code-Pfad ruft die Tabellen). R2 (Backend α/β:
Author-Profile + Publish + AI-Mod) als nächstes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:05:22 +02:00

312 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Marketplace-Restore — Playbook
> **Status:** Plan, R0+R1 in Arbeit. Stand 2026-05-09.
> **Vorgänger:** das alte `services/cards-server/` aus `managarten/` (mana-
> monorepo) wurde am 2026-05-08 zusammen mit `apps/cards/` dekommissioniert,
> weil beides eine Kopplung war — siehe Decommission-Commits
> `bc158cb0b` (cards-server), `9cd871749` (apps/cards), `dd1bab09d`
> (cards-core). Rollback-Tag: `cards-decommission-base` im managarten-Repo.
>
> Dieser Plan dokumentiert den **Restore** des Marketplace-Backends in
> die Standalone-Cards-App, additiv zur bestehenden Greenfield-API.
## TL;DR
- **Strategie-B-Klarstellung:** „Kein Code aus mana-monorepo" galt der
Study-/FSRS-/Sync-Schicht (Dexie raus, server-authoritative). Marketplace
war nie davon betroffen — er wurde nur mit-rausgerissen, weil er an
`apps/cards` gekoppelt war.
- **Restore vs. Neubau:** Restore. ~13.000 Zeilen reifer Code, 8 Phasen
in Produktion gelaufen, Plan-Doku in Goldstandard-Qualität (654 Zeilen),
bekannte Limitierungen sauber dokumentiert. Neubau wäre 46 Wochen,
Restore ~14 Tage.
- **Schema-Naming-Entscheidung:** **Eigenes `marketplace`-pgSchema**
in derselben `cards`-DB. Begründung: saubere Read/Write-Trennung,
Backup-Granularität (`pg_dump --schema=marketplace`), RLS-Policies
pro Schema möglich, keine Kollisionen mit den existierenden
`cards.{decks,cards,reviews,…}`-Tabellen.
- **Service-Topologie:** Single-Service. Marketplace-Routen kommen unter
`/api/v1/marketplace/*` in den bestehenden `cards/apps/api`. Kein
zweiter Hono-Prozess, kein zweiter Container. YAGNI bei deinem Volumen.
- **Frontend:** alte Routes 1:1 nach `cards/apps/web/src/routes/`
`/explore`, `/d/[slug]`, `/u/[slug]`, `/me/{published,subscribed,forks,purchases}`,
`/admin/reports`. Imports auf Verdaccio-Pakete umstellen, Theming-
Bridge-Aliase greifen automatisch.
- **Restore-Reihenfolge:** R0 (Doku) → R1 (Schema) → R2 (Auth + Routes
α/β) → R3 (γ + δ) → R4 (ε) → R5 (Frontend) → R6 (Smoke + erste
Cardecky-Decks publishen).
- **Was nicht im ersten Wurf:** Paid Decks (ζ.1) und Moderation-UI
(η.1). Schema-Tabellen kommen mit, aber Code-Pfade bleiben dormant
bis nach erstem Live-Test.
---
## Was die alte Implementation konnte (Phase α–η.1)
Inhalt der archivierten Dokumente — **Original-Wahrheit** unter
`cards/docs/marketplace/archive/`:
| Datei | Was drin |
|---|---|
| `MARKETPLACE_PLAN_2026-05-07.md` | 654-Zeilen-Vollvision: Datenmodell, mana-credits-Flow, Cold-Start-Strategie, Anti-Patterns, Phasen-Status |
| `COMPETITORS_2026-05.md` | 353-Zeilen-Konkurrenz-Analyse: Quizlet, AnkiHub, Brainscape, AnkiPro, AnkiApp, RemNote, Mnemosyne |
| `GUIDELINES.md` | 367 Zeilen Community-Guidelines + Lizenz-Modell (SPDX + Cardecky-Personal-Use-1.0 + Cardecky-Pro-Only-1.0) |
| `cards-server_CLAUDE.md` | 110 Zeilen Tech-Stack-Doku des Original-Service |
Phasen-Status zum Zeitpunkt der Decommission (2026-05-08):
| Phase | Status | Was lief produktiv |
|---|---|---|
| α — Skelett | ✅ live | 17-Tabellen-Schema, JWT-Auth, Container, Tunnel `cardecky-api.mana.how` |
| β — Author-Workflow | ✅ live | Profil-Claim, Publish, Lizenz-Picker (SPDX), Preis-Eingabe, AI-Mod-First-Pass |
| γ — Discovery | ✅ live | `/explore`, Stars, Follows, Author-Profile, Trending, Search (ILIKE) |
| δ — Subscribe + Smart-Merge | ✅ live | Pull, Diff-View „+N · ~N · N", FSRS-State erhalten über Karten-Hash-Diff |
| ε — PRs + Discussions | ✅ live | „✏️ Verbessern" auf jeder Karte, Author-Merge, Inline-Threads, Notify-Mails |
| ζ.1 — Paid Decks | ✅ live | 4-Schritt-Reserve→Purchase→Commit→Grant, 80/20-Split (90/10 für `verified_mana`) |
| η.1 — Moderation | ✅ live | Reports, Admin-Inbox, Takedown-Workflow (kaskadiert auf PRs), Author-Ban |
Bekannte Limitierungen (siehe `MARKETPLACE_PLAN_2026-05-07.md` §13a):
PR-Merge-stale-blind, Reconciler-Lücke bei Paid-Pipeline, Mention-System
fehlt, Discussion-Threading 1-Level, kein Refund-Self-Service. Alles
dokumentiert, nichts unbekannt.
---
## Architektur-Anpassungen für den Restore
### 1. DB-Topologie: eigenes `marketplace`-pgSchema
**Alt** (in `mana_platform`-DB, geteilt mit allen mana-Services):
```
mana_platform.cards.{authors, decks, deck_versions, …} — 17 Tabellen
```
**Neu** (in standalone `cards`-DB des Standalone-Repos):
```
cards.cards.{decks, cards, reviews, media_files, tags, imports} — Greenfield, bleibt
cards.marketplace.{authors, decks, deck_versions, deck_cards, …} — Restore, neu
```
Begründung für ein **eigenes pgSchema** statt Tabellen-Prefix
(`published_decks`, `published_deck_versions`, …):
- **Sauberer Read-Path.** Public-Endpoints sehen nur das `marketplace`-
Schema. Greenfield-Code (private Decks/Karten/Reviews) sieht
ausschließlich `cards`. Kein Risiko, dass ein `SELECT * FROM decks`
versehentlich beide Welten mischt.
- **Backup-Granularität.** `pg_dump --schema=marketplace` exportiert
nur den Marketplace-Stand für Compliance/Recovery. Privater Lern-
Stand der User bleibt unangetastet.
- **RLS-Policies pro Schema** — falls wir je Row-Level-Security
einführen für public-decks-take-down-Workflows, ist das pro Schema
konfigurierbar.
- **Drizzle-kit-Push-Disziplin.** `schemaFilter: ['cards', 'marketplace']`
hält beide Pushes sauber. Schema-Drift fängt sich auf Schema-Ebene.
Drizzle-Variablennamen halten den `public`-Prefix aus dem alten Code:
`publicDecks`, `publicDeckVersions`, `publicDeckCards`. So bleibt das
intent klar, und Imports kollidieren nicht mit `cards.decks` aus dem
Greenfield.
### 2. Auth-Modell: identisch, kein Refactor
Alter cards-server: JWKS-Cache gegen `mana-auth`. Greenfield-cards-api:
JWKS-Cache gegen `mana-auth`. Identisch. Service-Key-Auth (`X-Service-Key`)
für Mana-Webhooks ebenfalls 1:1 übernommen.
Optional-Auth-Middleware für Public-Endpoints (`/explore`, `/d/:slug`)
muss aus dem alten Code mitkommen — der erlaubt anonymen Read und
gibt zugleich personalisierte Daten (z.B. „Bist du Subscriber?")
wenn ein Bearer mit-übermittelt ist.
### 3. Service-Topologie: Single-Service
Alle Marketplace-Routen unter `/api/v1/marketplace/*` in
`cards/apps/api/src/routes/marketplace/`:
```
cards/apps/api/src/routes/marketplace/
├── authors.ts
├── decks.ts (publish, list, version reads)
├── engagement.ts (stars, subscribe, fork)
├── discussions.ts (card-discussions threads)
├── pull-requests.ts
├── moderation.ts (reports + admin)
├── purchases.ts (paid decks — dormant in R3, aktiv ab späterer Welle)
└── explore.ts (discovery + search)
```
Hauptserver-Mount in `cards/apps/api/src/index.ts`:
```ts
app.route('/api/v1/marketplace/authors', authorsRouter())
app.route('/api/v1/marketplace/decks', decksRouter())
// …
```
Vorteile: ein Prozess, ein Container, ein Tunnel-Endpoint
(`cardecky-api.mana.how`), eine JWT-Validierung, eine Drizzle-DB-Connection.
### 4. Frontend: additive Routes, gleicher Stack
`cards/apps/web/` ist SvelteKit + Svelte 5 (runes-only). Alter
`apps/cards/apps/web/` war es auch. Routen werden 1:1 übernommen,
mit drei Anpassungen:
- **Imports:** `@mana/shared-*` kommt heute aus Verdaccio (npm.mana.how).
`pnpm add @mana/shared-ui@^0.1.1 @mana/shared-share-protocol …`.
- **Theming:** alte Components nutzten alte Token. Greenfield-Bridge-
Aliase in `app.css` mappen die meisten alten Token aufs 12er-Mana-
Vokabular. Test im Browser, ggf. Anpassungen.
- **Auth-Hook:** `dev-stub.svelte.ts` bleibt für Phase-2-Lücke. Sobald
echte mana-auth-Login-Flow ausgerollt ist (siehe `STATUS.md`),
weicht der Stub für `@mana/shared-auth`.
### 5. Hash-Implementierung: bestehende `@cards/domain` benutzen
Alter `cards-server/src/lib/hash.ts`: eigenständige SHA-256-Implementierung.
Greenfield: `cardContentHash` in `@cards/domain` (Web-Crypto, deterministisch).
Beim Restore: `lib/hash.ts` **nicht** mit-übernehmen, sondern
Marketplace-Code auf `cardContentHash` aus `@cards/domain` umbiegen.
Eine Hash-Definition für die ganze App.
### 6. mana-Service-Calls: identische Pattern
Alter cards-server rief mana-credits, mana-llm, mana-media, mana-notify
über `MANA_*_URL`-env-Vars. Greenfield-cards-api macht das genauso.
Code 1:1 übernehmen.
---
## Wellen-Plan
| Welle | Zustand | Was passiert | Blocker |
|---|---|---|---|
| **R0** | 🟡 in Arbeit | Doku-Restore: Archive aus cards-decommission-base + dieser Plan + Strategie-B-Klarstellung | — |
| **R1** | ⏸ pending | Schema-Restore: 7 Schema-Files in `cards/apps/api/src/db/schema/marketplace/`, drizzle-kit push grün gegen lokale `cards`-DB | R0 |
| **R2** | ⏸ pending | Backend Phase α + β: Author-Profile + Publish + AI-Mod-Stub | R1 |
| **R3** | ⏸ pending | Backend Phase γ + δ: Discovery + Subscribe + Smart-Merge | R2 |
| **R4** | ⏸ pending | Backend Phase ε: Pull-Requests + Card-Discussions | R3 |
| **R5** | ⏸ pending | Frontend-Routes: `/explore`, `/d/[slug]`, `/u/[slug]`, `/me/{published,subscribed,forks}` | R4 |
| **R6** | ⏸ pending | E2E-Smoke: erstes Cardecky-Deck publishen, von Till's Account subscriben, Smart-Merge testen | R5 |
**Aufwand-Schätzung gesamt: ~14 Tage Real-Arbeit.**
Bewusst aus dem ersten Restore-Wurf rausgelassen:
- **ζ.1 Paid Decks** — Schema-Tabellen kommen in R1 mit, aber Routes/UI
bleiben dormant. Re-Aktivierung als eigene Welle nach Live-Validation.
Begründung: mana-credits-Integration ist heikel (4-step-Pipeline mit
reservation-commit-grant), Author-Erlöse sind ein Verein-Compliance-
Thema (Steuern, AGB, Refund-Policy), und solange Cardecky synthetic
Decks publisht, gibt's keinen Need.
- **η.1 Moderation-UI** — Schema-Tabellen + API-Endpoints kommen mit,
Admin-Frontend (`/admin/reports`) wird ausgelassen bis erste echte
User da sind. Take-Down via SQL für die ersten Wochen.
- **θ Deep AI** (Auto-Tags, Embeddings, TTS) — bleibt explizit später-
Phase, war im Original-Plan auch nicht für den ersten Wurf vorgesehen.
---
## Cardecky-Skill-Integration
Der `/cards-deck`-Skill (siehe `~/.claude/skills/cards-deck/SKILL.md`)
produziert Decks unter dem Cardecky-Plattform-User. Beim Marketplace-
Restore wird Cardecky **automatisch zum Marketplace-Author**:
1. Bei R2 wird ein Init-Skript einen `marketplace.authors`-Row für
Cardecky-User-ID anlegen (`slug='cardecky'`, `display_name='Cardecky'`,
`pseudonym=false`, `verified_mana=true` — der Verein vergibt das
Badge an seinen eigenen KI-Author).
2. Skill-Stufe 5 (Publish) wird erweitert um einen optionalen Schritt:
nach Anlegen des privaten Decks kann der Skill ein `POST
/api/v1/marketplace/decks/:id/publish` mit `semver=1.0.0` und einem
auto-generierten Changelog hinterher schicken — dann ist das Deck
sofort im `/explore` sichtbar.
3. Default bleibt aber „nur privat anlegen", weil:
- das Validate-Stage (Stufe 4) eine menschliche Sichtung verdient,
bevor 30 Karten öffentlich werden;
- Reviewer-Stops nach Stufe 3 sind zwingend.
Der Skill braucht keine Architektur-Änderung — er addiert nur einen
optionalen 6. Schritt.
---
## Lizenz-Modell (aus dem Original übernommen)
Aus `MARKETPLACE_PLAN_2026-05-07.md` §3 + `GUIDELINES.md`:
| SPDX-ID | Erlaubt | Wann |
|---|---|---|
| `CC0-1.0` | alles, kein Attribution-Pflicht | für Public-Domain-fähige Karten |
| `CC-BY-4.0` | alles, mit Attribution | meist gewählt für Wissens-Decks |
| `CC-BY-SA-4.0` | alles, ShareAlike + Attribution | für Wikipedia-derivierte Decks |
| `Cardecky-Personal-Use-1.0` | nur persönlicher Lern-Use, kein Re-Publish | **Default für kostenlose Decks** |
| `Cardecky-Pro-Only-1.0` | nur via Kauf, kein Re-Publish, kein Fork | **Pflicht für paid Decks** (DB-CHECK enforced) |
Der DB-CHECK auf `decks.price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0'`
ist im Schema beibehalten — Code-Bug kann nicht stillschweigend ein
Paid-Deck mit CC-Lizenz publishen.
---
## Cold-Start-Strategie (aus dem Original)
Kommt im ersten Restore-Wurf nicht aktiv zum Tragen, aber der Original-
Plan §9 hat drei Hebel definiert, die bei Re-Launch greifen:
1. **Verein-Seed-Decks** — 50 hochwertige Cardecky-published Decks
(Sprachen, Geschichte, Allgemeinwissen, Programmierung). Der
`/cards-deck`-Skill ist genau das Werkzeug dafür.
2. **Anki-Top-100-Import-Service** — populäre CC-BY-Anki-Decks mit
Original-Author-Attribution importieren, Original-Author bekommt
`verified_mana`-Badge bei Registrierung.
3. **Influencer-Outreach** — 1020 Anki-Power-Authoren (AnKing &
Konsorten) gezielt ansprechen, sehr Author-freundlicher Cut.
Hebel 2 + 3 sind Wachstumsmaßnahmen, nicht Phase-1. Hebel 1 ist
sofort umsetzbar mit dem bestehenden Skill.
---
## Anti-Patterns aus dem Original-Plan §13 (gelten weiter)
- **Kein 1-5-Sterne-Rating-System.** Stars (Bookmark) ja, Bewertungen nein.
- **Kein Reddit-Style-Voting** auf Karten/PRs/Discussions. Hacker-News-Effekt.
- **Kein „Karten der Woche" allein-algorithmisch.** Editorial + Trending-
Liste, aber niemals reiner Algo-Feed.
- **Kein Anki-Bashing im Marketing.** Bridge nicht Burning.
- **Keine Pflicht-Klarnamen.** Pseudonyme bleiben gleichberechtigt.
- **Kein Marketplace-Cut über 30 %.** Standard 80/20, verified 90/10.
---
## Offene Punkte
- **PR-Merge-stale-blind aus dem Original.** Bekannte Limitierung: wenn
Author zwischen PR-Open und Merge selbst eine Karte ändert, deren
`previousContentHash` der PR matched, gewinnt stumm der PR. Im
ersten Restore-Wurf so übernehmen wie war; späterer Fix via
optimistic locking auf `baseVersionId` der PR-Row mit Reject bei
Mismatch.
- **Reconciler-Cron für Paid-Pipeline-Inkonsistenzen.** Original §13a
beschreibt das Loch: bei Commit-/Grant-Failure nach Schritt 2 bleibt
eine Purchase-Row mit `creditsTransaction = null`. Beim Restore
initial nicht aktiv (Paid-Decks dormant), aber sobald Phase ζ
reaktiviert wird, ist Reconciler ein muss-haben.
- **`cards-decommission-base`-Tag im managarten-Repo.** Falls jemand
managarten löscht oder das Tag nochmal entfernt, geht der Restore-
Pfad verloren. Empfehlung: Schema-Files + ausgewählte Code-Snippets
einmal nach `cards/docs/marketplace/archive/` kopieren (Doks sind
schon drin, Code-Files folgen optional bei R1+R2).
- **Schema-Migrations-Pfad bei späterer Drizzle-Version.** Greenfield-
Cards plant Migration auf Drizzle 0.45/zod-4 mit der Plattform mit
(`mana/docs/MIGRATION_DRIZZLE_ZOD.md`). Marketplace-Schema kommt mit
Drizzle 0.38 — sollte mit upgradeen, idealerweise atomar.
- **Karten-Hash-Konsistenz zwischen Greenfield und Marketplace.**
Greenfield-`@cards/domain` `cardContentHash` ist die SoT. Marketplace-
`deck_cards.content_hash` muss mit demselben Algorithmus berechnet
werden — sonst funktioniert Smart-Merge nicht. Beim R2-Port-Pass
testen, dass `cardContentHash({type,fields})` aus `@cards/domain`
byte-identisch ist mit dem alten `cards-server/src/lib/hash.ts`. Wenn
nicht: alten Code anpassen, nicht `@cards/domain` brechen.