pageta/CLAUDE.md
Till JS 45e118b201 sync2: der serverseitige Schreibpfad verschluesselt jetzt auch
Bis heute schrieb `emit()` in apps/api/src/lib/sync2-pageta.ts Klartext:
`clientsFor(jwt)` baute den Vault-Crypto-Provider und `const { http } = ...`
warf ihn weg. Der Web-Client verschluesselt seit `applySyncConsent` — also
lag dasselbe Event-Type in mana_sync_v2.pageta.events in beiden Formen,
329 Zeilen davon im Klartext, darunter `content` und `userNote` ganzer
Artikel. Betroffene Aufrufer: MCP-Tool save_article, imports-worker,
routes/articles.ts.

Warum verschluesseln hier richtig und nicht bloss moeglich ist: Client und
API benutzen denselben `createMasterKeyProviderFromVault`. Der Lesepfad
(pullAll -> decryptPayload) holt den Schluessel ohnehin. Verschluesseln
beim Schreiben oeffnet also KEINE neue Vertrauensgrenze, es stellt die
fehlende Symmetrie her. Die verworfenen Alternativen: die Felder fuer
nicht-vertraulich erklaeren (Lesehistorie und Notizen sind das Gegenteil)
oder Key-Escrow (gibt Zero-Knowledge auf).

Fail-closed: steht der Vault im zero-knowledge-Modus, wirft
createMasterKeyProviderFromVault und es wird NICHTS geschrieben. Ein
Server, der nicht verschluesseln kann, darf nicht ersatzweise Klartext
ablegen.

Der Altbestand bleibt lesbar — decryptPayload reicht Klartext durch.

CLAUDE.md Invariante 4 sagte noch "Encryption initial AUS ... heute
plaintext". Sie war da schon falsch und hat den Fehler drei Monate am
Leben gehalten; jetzt beschreibt sie den Ist-Zustand samt Fail-closed-
Regel und traegt den Vorfall als Warnung.

Vier Regressionstests, gegengeprueft: ohne den Fix fallen drei davon, und
der Fehlertext zeigt woertlich den Artikeltext im Envelope.
2026-08-02 03:00:54 +02:00

6.6 KiB

CLAUDE.md — pageta repo

Guidance für Claude Code in diesem Repository.

Wenn du gerade neu bist: lies zuerst STATUS.md — dort steht der aktuelle Phasen-Stand und was offen ist. Dieses CLAUDE.md ist nur die Konventions- und Architektur-Referenz.

Was dieses Repo ist

Pageta — eigenständige Reader-Webapp des Vereins mana Aktiver Kern-Use-Case:

  1. Lese-Liste — Pocket-Style Save-it-later mit Highlights, Tags, Bulk-Import bis 200 URLs, einmaliger Blog/Feed-Archiv-Import (RSS-Paging + optional WP-Sitemap, bis 1000 Artikel) unter /lese-liste/settings.

Feed (kuratierte News aus dem Plattform-Pool) und Recherche (RSS-Feed-Discovery) sind seit 2026-06-01 dormant — aus der Nav genommen, Code bleibt im Repo. Feed wegen Werte-Spannung zum Engagement-Feed-Verbot; Recherche, weil angepinnte Feeds keinen Lese-Ort hatten (der Feed fetchte customFeeds nie). Begründung + Revival-Bedingung (mana-discover-Andockung): docs/FEED.md.

Pageta ist eine Peer-App im mana-Ökosystem: redet HTTP/JWT mit mana-auth, mana-news-pool, mana-research, mana-share, mana-credits. Eigene DB mana_pageta, eigenes Frontend, eigene Deploy-Pipeline.

                  HTTP/JWT      ┌──────────────┐
   mana   ◄─────────────── │  pageta/     │  Postgres `mana_pageta`
   Plattform                    │  (this repo) │  pageta.mana.how
   (auth/credits/share/etc.)    └──────────────┘

Status

LIVE 🚀 + voll-featured (λ-0..λ-6 ✅). App unter app.pageta.com, API unter pageta-api.mana.how, Marketing-Landing auf pageta.com. Aktueller Detail-Stand + offene Punkte: STATUS.md.

Konventionen

  • pnpm 9.15.x, Node 20+, Turborepo 2.x
  • Tabs für Indent, single quotes, 100-col Prettier (.prettierrc.json ist die Source of Truth)
  • Stack: SvelteKit 2 + Svelte 5 (runes-only) für Web, Hono + Bun + Drizzle für API, Postgres mit pgSchema('reader')
  • Drizzle 0.38 / drizzle-kit 0.30 / zod 3 — Parität zur managarten- und mana-Plattform-Toolchain
  • JWT-Validation lokal via JWKS-Cache (5 min), niemals Live-Call zu mana-auth pro Request
  • Service-to-Service-Calls via X-Service-Key
  • Tests: Vitest für Unit/Integration
  • Formatierung: pnpm run format vor jedem Commit

Architektonische Invarianten

  1. Event-sourced, local-first. User-owned-Daten (Lese-Liste, Highlights, Tags, Warnungen) laufen seit der G-3-Migration (2026-05-19) über sync2.mana.how mit lokalem EventLog/Outbox via @mana/event-sync — nicht mehr server-authoritative. HTTP-only gegen die pageta-API bleiben nur stateless-Compute (Feed, /articles/extract/preview, /summarize, /research). Die retired user-CRUD-Routen antworten 410.
  2. Eigene Postgres-DB mana_pageta mit Schema-Isolation via pgSchema('reader').
  3. Föderation über @mana/shared-app-tpl. Pflicht-Endpoints (/.well-known/mana-app.json, /healthz, /api/v1/share/receive, /api/v1/tools/:name, /api/v1/search, /api/v1/dsgvo/...) kommen aus dem geteilten Boilerplate.
  4. Encryption ist AN — auf JEDEM Schreibpfad. Payloads der user-owned-Events werden mit dem Vault-Master-Key des Users verschlüsselt (enc:1:…). Das gilt nicht nur für den Web-Client (applySyncConsent in apps/web/src/lib/sync.svelte.ts), sondern genauso für den serverseitigen Pfad in apps/api/src/lib/sync2-pageta.ts (MCP-Tools, imports-worker, routes/articles.ts). Beide benutzen denselben createMasterKeyProviderFromVault — ein Server-Schreiber öffnet also keine zusätzliche Vertrauensgrenze, aber er muss verschlüsseln. Fail-closed: kann der Vault den Schlüssel nicht liefern (Zero-Knowledge-Modus), schlägt der Schreibvorgang fehl — niemals ersatzweise Klartext ablegen.

    Diese Invariante hiess bis 2026-08-02 „Encryption initial AUS … heute plaintext". Sie war da schon falsch: der Client verschlüsselte längst, nur der API-Pfad nicht. Ergebnis waren 329 Klartext-Events (mit content und userNote) in mana_sync_v2.pageta.events. Wer die Verschlüsselung anfasst, aktualisiert diesen Absatz mit.

  5. Keine externe SaaS, wo vermeidbar. Erlaubte Ausnahmen kommen über mana-Plattform.
  6. Aggregator-Policy gilt für kuratierten Feed-Pool und RSS-Recherche — siehe mana/docs/AGGREGATOR_POLICY.md. User-Agent ManaPageta/0.1 (+https://pageta.mana.how), robots.txt-Respekt.

Repo-Struktur

pageta/
├── apps/
│   ├── api/                  Hono+Bun-Backend (Port 3099)
│   └── web/                  SvelteKit-Frontend (Port 3100)
├── packages/
│   └── pageta-domain/         Pure-TS: Feed-Engine-Scoring, Types,
│                             Source-Meta-Liste
├── infrastructure/           docker-compose, tunnel-config
├── docs/                     DOMAIN.md, etc.
├── app-manifest.json         Source of Truth für Föderation
└── .github/workflows/        ci.yml, deploy.yml

@mana/* Konsumation

Pageta zieht alle Shared-Pakete aus Verdaccio (pkg.mana.how):

  • @mana/shared-share-protocol — Manifest, Envelope-Schemas
  • @mana/shared-app-tpl — Pflicht-Endpoint-Boilerplate
  • @mana/shared-hono — Auth-Middleware, Health-Routes, Error-Handler
  • @mana/shared-auth — Client-SDK für Login-Flow
  • @mana/shared-rss — RSS-Parsing + Readability (nach Lift-A; bis dahin vendored in apps/api/src/lib/shared-rss/)
  • @mana/shared-types, @mana/shared-icons, @mana/shared-i18n, @mana/shared-theme, @mana/shared-tailwind
  • @mana/themes — Theme-Variant (TBD)
  • @mana/shared-ui-2 — Greenfield-Komponenten

Wichtige Cross-Repo-Doks

  • mana/docs/FEDERATION.md — Architektur-Grundlagen
  • mana/docs/SHARE_PROTOCOL.md — Manifest- und Envelope-Schemas
  • mana/docs/MANA_AUTH_FEDERATION.md — App-Identitäts-Modell
  • mana/docs/SHARED_PACKAGES.md — Versionierungs-Disziplin
  • mana/docs/PORTS.md — Port-Allokation (pageta-api: 3099, pageta-web: 3100)
  • mana/docs/AGGREGATOR_POLICY.md — Crawl- + Source-Policy

Lokal entwickeln

pnpm install
pnpm docker:up                 # Lokales Postgres :5443
pnpm --filter @pageta/api db:push
pnpm dev                       # api + web parallel

Voraussetzungen:

  • mana-Plattform-Stack lokal laufend (mana-auth zumindest für Login- Test), oder Auth via JWT-Stub in DEV.
  • Verdaccio-Token in ~/.npmrc (npm login --registry=https://pkg.mana.how)