From aff4d9536a4a1214af184df995d1dc8c2e0f18f3 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 8 May 2026 18:00:56 +0200 Subject: [PATCH] =?UTF-8?q?Phase=209d:=20Pre-Flight=20=E2=80=94=20Protocol?= =?UTF-8?q?-Mirror=20durch=20upstream=20ersetzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @mana/shared-share-protocol@0.1.0 ist jetzt installierbar (NPM_AUTH_TOKEN aus claudebot-Verdaccio-Account). Lokaler protocol/-Mirror zeigt jetzt auf upstream: - envelope.ts → Re-Export von ShareEnvelopeSchema/Strict, parseEnvelope, ENVELOPE_VERSION, ShareEnvelope - search.ts → Re-Export von SearchHitSchema, SearchResultEnvelopeSchema, SEARCH_ENVELOPE_VERSION, SearchHit, SearchResultEnvelope - payloads.ts → Re-Export der Format-Schemas (Quote/Link/Text); Cards-spezifische PAYLOAD_SCHEMAS / validatePayloadForType bleiben lokal (Akzeptanz-Liste ist Cards-Layer, nicht Föderation) Spec-Drift gefixt: der frühere Mirror nutzte MANA_TYPE_URL = 'mana/url', upstream definiert MANA_TYPE_LINK = 'mana/link'. app-manifest.json, share-handlers (UrlPayload → LinkPayload, "mana/url" → "mana/link") und Doku-Kommentare auf den Spec-konformen Namen umgestellt. DNS-Korrektur in Repo-.npmrc: pkg.mana.how-Tunnel ist Lame-Duck (404), npm.mana.how ist die produktive Verdaccio-Route nach 2026-05-07-Re-Deploy. ~/.npmrc bleibt unangetastet — Anpassung ist user-side. Tests + svelte-check 0 errors, 92 Tests grün (41 Domain + 46 API + 5 Web), prod-Build sauber. Co-Authored-By: Claude Opus 4.7 (1M context) --- .npmrc | 11 ++- app-manifest.json | 2 +- apps/api/src/share-handlers/index.ts | 6 +- packages/cards-domain/package.json | 1 + .../cards-domain/src/protocol/envelope.ts | 80 +++-------------- .../cards-domain/src/protocol/payloads.ts | 89 +++++++------------ packages/cards-domain/src/protocol/search.ts | 48 +++------- pnpm-lock.yaml | 11 +++ 8 files changed, 78 insertions(+), 170 deletions(-) diff --git a/.npmrc b/.npmrc index 5ce0b4c..d7b34e8 100644 --- a/.npmrc +++ b/.npmrc @@ -1,7 +1,10 @@ # Cards consumes @mana/* shared packages from the Verein's private -# Verdaccio registry (pkg.mana.how on Mac Mini). -# Local dev: run `npm login --registry=https://pkg.mana.how` once; +# Verdaccio registry (npm.mana.how — Mac Mini, Container mana-verdaccio +# auf :4873). Stand 2026-05-08: pkg.mana.how-Tunnel ist Lame-Duck und +# antwortet 404, npm.mana.how ist die produktive Route. +# +# Local dev: run `npm login --registry=https://npm.mana.how` once; # the resulting ~/.npmrc token is read via $NPM_AUTH_TOKEN substitution. # Production CI: set NPM_AUTH_TOKEN in workflow secrets. -@mana:registry=https://pkg.mana.how/ -//pkg.mana.how/:_authToken=${NPM_AUTH_TOKEN} +@mana:registry=https://npm.mana.how/ +//npm.mana.how/:_authToken=${NPM_AUTH_TOKEN} diff --git a/app-manifest.json b/app-manifest.json index 3700a1e..3b71fbb 100644 --- a/app-manifest.json +++ b/app-manifest.json @@ -32,7 +32,7 @@ "shares": [{ "type": "mana/card", "schema_ref": "/payload-schemas/card.json" }], "accepts": [ { "type": "mana/quote", "handler": "create_card_from_quote" }, - { "type": "mana/url", "handler": "save_link_as_card" }, + { "type": "mana/link", "handler": "save_link_as_card" }, { "type": "mana/text", "handler": "create_card_from_text" } ], "tools": [ diff --git a/apps/api/src/share-handlers/index.ts b/apps/api/src/share-handlers/index.ts index 6fa7443..54ada9e 100644 --- a/apps/api/src/share-handlers/index.ts +++ b/apps/api/src/share-handlers/index.ts @@ -1,5 +1,5 @@ import { newReview, subIndexCount } from '@cards/domain'; -import type { QuotePayload, TextPayload, UrlPayload } from '@cards/domain'; +import type { QuotePayload, TextPayload, LinkPayload } from '@cards/domain'; import type { CardsDb } from '../db/connection.ts'; import { cards, reviews } from '../db/schema/index.ts'; @@ -89,13 +89,13 @@ export async function createCardFromQuote( } /** - * mana/url → Karte. Front = Titel (oder URL falls kein Titel), + * mana/link → Karte. Front = Titel (oder URL falls kein Titel), * Back = URL + Description/Snippet. */ export async function saveLinkAsCard( db: CardsDb, userId: string, - payload: UrlPayload + payload: LinkPayload ): Promise { const front = payload.title ?? payload.url; const summary = payload.description ?? payload.snippet ?? ''; diff --git a/packages/cards-domain/package.json b/packages/cards-domain/package.json index 7af0d6c..2029de2 100644 --- a/packages/cards-domain/package.json +++ b/packages/cards-domain/package.json @@ -20,6 +20,7 @@ "clean": "rm -rf dist .turbo coverage" }, "dependencies": { + "@mana/shared-share-protocol": "^0.1.0", "ts-fsrs": "^5.3.2", "zod": "3" }, diff --git a/packages/cards-domain/src/protocol/envelope.ts b/packages/cards-domain/src/protocol/envelope.ts index ea0bc48..b14c469 100644 --- a/packages/cards-domain/src/protocol/envelope.ts +++ b/packages/cards-domain/src/protocol/envelope.ts @@ -1,69 +1,15 @@ -// TEMPORARY MIRROR von @mana/shared-share-protocol/envelope. +// Re-Export der Föderations-Envelope-Schemas aus +// `@mana/shared-share-protocol` (Klasse-A-Vertrag der Föderation). // -// Solange `pkg.mana.how` für Cards nicht erreichbar ist (NPM_AUTH_TOKEN -// in `~/.npmrc` fehlt), halten wir die Schemas hier lokal. Sobald -// Verdaccio offen ist, wird diese Datei gegen einen Re-Export aus -// `@mana/shared-share-protocol` getauscht — alle Imports sind in der -// Form `import { ShareEnvelopeSchema } from '@cards/domain'` formuliert, -// damit der Swap eine reine 1-Liner-Edit-Aufgabe ist. -// -// Bei jedem Update der mana-Spec muss diese Datei nachgezogen werden, -// bis der Swap erfolgt. Stand: 2026-05-08, ENVELOPE_VERSION 0.1. +// Die Datei war bis 2026-05-08 ein lokaler Mirror, weil das Verdaccio- +// Token noch nicht im Cards-Repo verfügbar war. Mit Sprint 8 (Pre- +// Flight) ist `npm.mana.how` produktiv und wir konsumieren die Quelle +// direkt — kein Drift-Risiko mehr. -import { z } from 'zod'; - -export const ENVELOPE_VERSION = '0.1' as const; - -const ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/; -const TYPE_NAME_REGEX = /^mana\/[a-z][a-z0-9-]+$/; - -const SignatureSchema = z.object({ - algorithm: z.literal('eddsa'), - key_id: z.string().min(1), - signature: z.string().min(1), -}); - -export const ShareEnvelopeSchema = z.object({ - envelope_version: z.literal(ENVELOPE_VERSION), - share_id: z.string().regex(ULID_REGEX, 'must be ULID'), - - from: z.object({ - app: z.string().min(1), - app_version: z.string().min(1), - user_id: z.string().uuid(), - timestamp: z.string().datetime(), - instance_id: z.string().max(120).optional(), - }), - - to: z.object({ - app: z.string().min(1), - user_id: z.string().uuid(), - }), - - type: z.string().regex(TYPE_NAME_REGEX, 'must be "mana/"'), - payload: z.unknown(), - - source_link: z.string().max(2000).optional(), - user_note: z.string().max(500).optional(), - ttl_seconds: z.number().int().positive().optional(), - - intent: z.enum(['user_action', 'automation', 'agent_tool']).default('user_action'), - consent_recorded_at: z.string().datetime(), - - signature: SignatureSchema.optional(), -}); - -export type ShareEnvelope = z.infer; - -/** Strict-Variante: Cross-User-Shares hart verboten. */ -export const ShareEnvelopeStrictSchema = ShareEnvelopeSchema.refine( - (env) => env.from.user_id === env.to.user_id, - { - message: 'cross-user shares forbidden — from.user_id must equal to.user_id', - path: ['to', 'user_id'], - } -); - -export function parseEnvelope(raw: unknown) { - return ShareEnvelopeStrictSchema.safeParse(raw); -} +export { + ENVELOPE_VERSION, + ShareEnvelopeSchema, + ShareEnvelopeStrictSchema, + parseEnvelope, + type ShareEnvelope, +} from '@mana/shared-share-protocol'; diff --git a/packages/cards-domain/src/protocol/payloads.ts b/packages/cards-domain/src/protocol/payloads.ts index c286c02..3f700a2 100644 --- a/packages/cards-domain/src/protocol/payloads.ts +++ b/packages/cards-domain/src/protocol/payloads.ts @@ -1,65 +1,38 @@ -// TEMPORARY MIRROR — siehe envelope.ts. +// Cards-spezifischer Payload-Adapter über `@mana/shared-share-protocol`. // -// Payload-Schemas für die `accepts[]` aus dem Cards-Manifest: -// `mana/quote`, `mana/url`, `mana/text`. Andere known types -// (`mana/transcript`, `mana/link`, `mana/note`, etc.) werden wir -// erst ergänzen, wenn Cards sie aktiv akzeptiert. +// Die Format-Schemas (Quote, Link, Text) kommen aus dem Föderations- +// Vertrag — Cards selbst hält nur die Akzeptanz-Liste und einen kleinen +// Validate-Helper, weil das ein Cards-internes Layer ist. +// +// Hinweis: der frühere lokale Mirror nutzte `MANA_TYPE_URL = 'mana/url'`. +// Der Spec-konforme Name ist `mana/link` — beim Pre-Flight-Swap am +// 2026-05-08 wurde das app-manifest.json + Handler-Code mit umgestellt. -import { z } from 'zod'; +export { + MANA_TYPE_QUOTE, + MANA_TYPE_LINK, + MANA_TYPE_TEXT, + QuotePayloadSchema, + LinkPayloadSchema, + TextPayloadSchema, + type QuotePayload, + type LinkPayload, + type TextPayload, +} from '@mana/shared-share-protocol'; -export const MANA_TYPE_QUOTE = 'mana/quote' as const; -export const MANA_TYPE_URL = 'mana/url' as const; -export const MANA_TYPE_TEXT = 'mana/text' as const; +import { + MANA_TYPE_QUOTE, + MANA_TYPE_LINK, + MANA_TYPE_TEXT, + QuotePayloadSchema, + LinkPayloadSchema, + TextPayloadSchema, +} from '@mana/shared-share-protocol'; -export const QuotePayloadSchema = z - .object({ - text: z.string().min(1).max(8000), - source: z.string().max(500).optional(), - source_url: z.string().url().optional(), - source_kind: z - .enum(['book', 'article', 'talk', 'conversation', 'transcript', 'link', 'manual', 'other']) - .optional(), - language: z - .string() - .regex(/^[a-z]{2}(-[A-Z]{2})?$/) - .optional(), - tags: z.array(z.string().max(64)).max(50).optional(), - }) - .strict(); -export type QuotePayload = z.infer; - -/** - * `mana/url` — externe Web-Adresse mit optionalen OG-Metadaten. - * (Mana-Spec nennt dies `mana/link`. Wir akzeptieren dieselben - * Felder, aber unter unserem deklarierten Type-Namen `mana/url`.) - */ -export const UrlPayloadSchema = z - .object({ - url: z.string().url(), - title: z.string().max(500).optional(), - description: z.string().max(2000).optional(), - snippet: z.string().max(2000).optional(), - image_url: z.string().url().optional(), - site_name: z.string().max(200).optional(), - favicon_url: z.string().url().optional(), - }) - .strict(); -export type UrlPayload = z.infer; - -export const TextPayloadSchema = z - .object({ - text: z.string().min(1).max(50000), - format: z.enum(['plain', 'markdown', 'html']).default('plain'), - source: z.string().max(500).optional(), - source_url: z.string().url().optional(), - }) - .strict(); -export type TextPayload = z.infer; - -/** Map vom Type-String zum Payload-Schema. */ +/** Map vom Type-String zum Payload-Schema, beschränkt auf was Cards akzeptiert. */ export const PAYLOAD_SCHEMAS = { [MANA_TYPE_QUOTE]: QuotePayloadSchema, - [MANA_TYPE_URL]: UrlPayloadSchema, + [MANA_TYPE_LINK]: LinkPayloadSchema, [MANA_TYPE_TEXT]: TextPayloadSchema, } as const; @@ -68,7 +41,9 @@ export type AcceptedShareType = keyof typeof PAYLOAD_SCHEMAS; export function validatePayloadForType( type: string, payload: unknown -): { success: true; data: unknown } | { success: false; error: 'unknown_type' | 'invalid_payload'; issues?: string[] } { +): + | { success: true; data: unknown } + | { success: false; error: 'unknown_type' | 'invalid_payload'; issues?: string[] } { const schema = PAYLOAD_SCHEMAS[type as AcceptedShareType]; if (!schema) return { success: false, error: 'unknown_type' }; const r = schema.safeParse(payload); diff --git a/packages/cards-domain/src/protocol/search.ts b/packages/cards-domain/src/protocol/search.ts index 06bebe8..80ecd46 100644 --- a/packages/cards-domain/src/protocol/search.ts +++ b/packages/cards-domain/src/protocol/search.ts @@ -1,39 +1,11 @@ -// TEMPORARY MIRROR — siehe envelope.ts. +// Re-Export der Search-Result-Envelope-Schemas aus +// `@mana/shared-share-protocol`. Lokaler Mirror seit 2026-05-08 ersetzt +// (siehe envelope.ts für den Kontext). -import { z } from 'zod'; - -export const SEARCH_ENVELOPE_VERSION = '0.1' as const; - -export const SearchHitSchema = z.object({ - id: z.string().min(1), - type: z.string().min(1), - title: z.string().min(1).max(300), - snippet: z.string().max(1000).optional(), - link: z.string().min(1), - score: z.number().min(0).max(1), - highlights: z - .array( - z.object({ - field: z.string().max(80), - fragment: z.string().max(500), - }) - ) - .max(10) - .optional(), - meta: z.record(z.string(), z.unknown()).optional(), - created_at: z.string().datetime().optional(), - updated_at: z.string().datetime().optional(), -}); -export type SearchHit = z.infer; - -export const SearchResultEnvelopeSchema = z.object({ - envelope_version: z.literal(SEARCH_ENVELOPE_VERSION), - query: z.string().min(1).max(500), - app: z.string().min(1), - app_version: z.string().min(1), - results: z.array(SearchHitSchema).max(200), - total: z.number().int().nonnegative(), - partial: z.boolean(), - took_ms: z.number().int().nonnegative(), -}); -export type SearchResultEnvelope = z.infer; +export { + SEARCH_ENVELOPE_VERSION, + SearchHitSchema, + SearchResultEnvelopeSchema, + type SearchHit, + type SearchResultEnvelope, +} from '@mana/shared-share-protocol'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00a3a37..0973606 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,6 +112,9 @@ importers: packages/cards-domain: dependencies: + '@mana/shared-share-protocol': + specifier: ^0.1.0 + version: 0.1.0 ts-fsrs: specifier: ^5.3.2 version: 5.3.2 @@ -560,6 +563,10 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mana/shared-share-protocol@0.1.0': + resolution: {integrity: sha512-I1fIDbS3nu++9LUXc08ICrLXE/cdV/n9D0Jm8LOhVH9izUXQSSg2EO4M2+m7K5vc5KdjGBcYrFPhAg48+KE6Kw==} + hasBin: true + '@petamoriken/float16@3.9.3': resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} @@ -1868,6 +1875,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mana/shared-share-protocol@0.1.0': + dependencies: + zod: 3.25.76 + '@petamoriken/float16@3.9.3': {} '@polka/url@1.0.0-next.29': {}