Phase 9d: Pre-Flight — Protocol-Mirror durch upstream ersetzt
@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) <noreply@anthropic.com>
This commit is contained in:
parent
47419b3cac
commit
aff4d9536a
8 changed files with 78 additions and 170 deletions
11
.npmrc
11
.npmrc
|
|
@ -1,7 +1,10 @@
|
||||||
# Cards consumes @mana/* shared packages from the Verein's private
|
# Cards consumes @mana/* shared packages from the Verein's private
|
||||||
# Verdaccio registry (pkg.mana.how on Mac Mini).
|
# Verdaccio registry (npm.mana.how — Mac Mini, Container mana-verdaccio
|
||||||
# Local dev: run `npm login --registry=https://pkg.mana.how` once;
|
# 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.
|
# the resulting ~/.npmrc token is read via $NPM_AUTH_TOKEN substitution.
|
||||||
# Production CI: set NPM_AUTH_TOKEN in workflow secrets.
|
# Production CI: set NPM_AUTH_TOKEN in workflow secrets.
|
||||||
@mana:registry=https://pkg.mana.how/
|
@mana:registry=https://npm.mana.how/
|
||||||
//pkg.mana.how/:_authToken=${NPM_AUTH_TOKEN}
|
//npm.mana.how/:_authToken=${NPM_AUTH_TOKEN}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
"shares": [{ "type": "mana/card", "schema_ref": "/payload-schemas/card.json" }],
|
"shares": [{ "type": "mana/card", "schema_ref": "/payload-schemas/card.json" }],
|
||||||
"accepts": [
|
"accepts": [
|
||||||
{ "type": "mana/quote", "handler": "create_card_from_quote" },
|
{ "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" }
|
{ "type": "mana/text", "handler": "create_card_from_text" }
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { newReview, subIndexCount } from '@cards/domain';
|
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 type { CardsDb } from '../db/connection.ts';
|
||||||
import { cards, reviews } from '../db/schema/index.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.
|
* Back = URL + Description/Snippet.
|
||||||
*/
|
*/
|
||||||
export async function saveLinkAsCard(
|
export async function saveLinkAsCard(
|
||||||
db: CardsDb,
|
db: CardsDb,
|
||||||
userId: string,
|
userId: string,
|
||||||
payload: UrlPayload
|
payload: LinkPayload
|
||||||
): Promise<HandlerResult> {
|
): Promise<HandlerResult> {
|
||||||
const front = payload.title ?? payload.url;
|
const front = payload.title ?? payload.url;
|
||||||
const summary = payload.description ?? payload.snippet ?? '';
|
const summary = payload.description ?? payload.snippet ?? '';
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"clean": "rm -rf dist .turbo coverage"
|
"clean": "rm -rf dist .turbo coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mana/shared-share-protocol": "^0.1.0",
|
||||||
"ts-fsrs": "^5.3.2",
|
"ts-fsrs": "^5.3.2",
|
||||||
"zod": "3"
|
"zod": "3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Die Datei war bis 2026-05-08 ein lokaler Mirror, weil das Verdaccio-
|
||||||
// in `~/.npmrc` fehlt), halten wir die Schemas hier lokal. Sobald
|
// Token noch nicht im Cards-Repo verfügbar war. Mit Sprint 8 (Pre-
|
||||||
// Verdaccio offen ist, wird diese Datei gegen einen Re-Export aus
|
// Flight) ist `npm.mana.how` produktiv und wir konsumieren die Quelle
|
||||||
// `@mana/shared-share-protocol` getauscht — alle Imports sind in der
|
// direkt — kein Drift-Risiko mehr.
|
||||||
// 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.
|
|
||||||
|
|
||||||
import { z } from 'zod';
|
export {
|
||||||
|
ENVELOPE_VERSION,
|
||||||
export const ENVELOPE_VERSION = '0.1' as const;
|
ShareEnvelopeSchema,
|
||||||
|
ShareEnvelopeStrictSchema,
|
||||||
const ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
parseEnvelope,
|
||||||
const TYPE_NAME_REGEX = /^mana\/[a-z][a-z0-9-]+$/;
|
type ShareEnvelope,
|
||||||
|
} from '@mana/shared-share-protocol';
|
||||||
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/<kind>"'),
|
|
||||||
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<typeof ShareEnvelopeSchema>;
|
|
||||||
|
|
||||||
/** 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);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
// Die Format-Schemas (Quote, Link, Text) kommen aus dem Föderations-
|
||||||
// `mana/quote`, `mana/url`, `mana/text`. Andere known types
|
// Vertrag — Cards selbst hält nur die Akzeptanz-Liste und einen kleinen
|
||||||
// (`mana/transcript`, `mana/link`, `mana/note`, etc.) werden wir
|
// Validate-Helper, weil das ein Cards-internes Layer ist.
|
||||||
// erst ergänzen, wenn Cards sie aktiv akzeptiert.
|
//
|
||||||
|
// 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;
|
import {
|
||||||
export const MANA_TYPE_URL = 'mana/url' as const;
|
MANA_TYPE_QUOTE,
|
||||||
export const MANA_TYPE_TEXT = 'mana/text' as const;
|
MANA_TYPE_LINK,
|
||||||
|
MANA_TYPE_TEXT,
|
||||||
|
QuotePayloadSchema,
|
||||||
|
LinkPayloadSchema,
|
||||||
|
TextPayloadSchema,
|
||||||
|
} from '@mana/shared-share-protocol';
|
||||||
|
|
||||||
export const QuotePayloadSchema = z
|
/** Map vom Type-String zum Payload-Schema, beschränkt auf was Cards akzeptiert. */
|
||||||
.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<typeof QuotePayloadSchema>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `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<typeof UrlPayloadSchema>;
|
|
||||||
|
|
||||||
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<typeof TextPayloadSchema>;
|
|
||||||
|
|
||||||
/** Map vom Type-String zum Payload-Schema. */
|
|
||||||
export const PAYLOAD_SCHEMAS = {
|
export const PAYLOAD_SCHEMAS = {
|
||||||
[MANA_TYPE_QUOTE]: QuotePayloadSchema,
|
[MANA_TYPE_QUOTE]: QuotePayloadSchema,
|
||||||
[MANA_TYPE_URL]: UrlPayloadSchema,
|
[MANA_TYPE_LINK]: LinkPayloadSchema,
|
||||||
[MANA_TYPE_TEXT]: TextPayloadSchema,
|
[MANA_TYPE_TEXT]: TextPayloadSchema,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
@ -68,7 +41,9 @@ export type AcceptedShareType = keyof typeof PAYLOAD_SCHEMAS;
|
||||||
export function validatePayloadForType(
|
export function validatePayloadForType(
|
||||||
type: string,
|
type: string,
|
||||||
payload: unknown
|
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];
|
const schema = PAYLOAD_SCHEMAS[type as AcceptedShareType];
|
||||||
if (!schema) return { success: false, error: 'unknown_type' };
|
if (!schema) return { success: false, error: 'unknown_type' };
|
||||||
const r = schema.safeParse(payload);
|
const r = schema.safeParse(payload);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
SEARCH_ENVELOPE_VERSION,
|
||||||
export const SEARCH_ENVELOPE_VERSION = '0.1' as const;
|
SearchHitSchema,
|
||||||
|
SearchResultEnvelopeSchema,
|
||||||
export const SearchHitSchema = z.object({
|
type SearchHit,
|
||||||
id: z.string().min(1),
|
type SearchResultEnvelope,
|
||||||
type: z.string().min(1),
|
} from '@mana/shared-share-protocol';
|
||||||
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<typeof SearchHitSchema>;
|
|
||||||
|
|
||||||
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<typeof SearchResultEnvelopeSchema>;
|
|
||||||
|
|
|
||||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
|
|
@ -112,6 +112,9 @@ importers:
|
||||||
|
|
||||||
packages/cards-domain:
|
packages/cards-domain:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@mana/shared-share-protocol':
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0
|
||||||
ts-fsrs:
|
ts-fsrs:
|
||||||
specifier: ^5.3.2
|
specifier: ^5.3.2
|
||||||
version: 5.3.2
|
version: 5.3.2
|
||||||
|
|
@ -560,6 +563,10 @@ packages:
|
||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
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':
|
'@petamoriken/float16@3.9.3':
|
||||||
resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==}
|
resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==}
|
||||||
|
|
||||||
|
|
@ -1868,6 +1875,10 @@ snapshots:
|
||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@mana/shared-share-protocol@0.1.0':
|
||||||
|
dependencies:
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
'@petamoriken/float16@3.9.3': {}
|
'@petamoriken/float16@3.9.3': {}
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.29': {}
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue