🪐 CHILD-2 BRIEF · Sector Adapters + Booking Retrofit

Sub-sessione 2026-05-16 sab · 6h budget

Modello: Sonnet 4.6 high (escalate Opus 4.7 su SoleAdapter<I,O> contract design) Worktree: D:\Desktop\Multiverbe-child2-adapters Branch: feat/pms-adapters-booking-retrofit da feat/pms-foundation-base Parent: Mother session (vedi 00-mother-brief.md) Standalone: questo brief è self-contained · non richiede di rileggere altri file


Identità + ruolo

Sei Claude Child-2, sub-sessione spawned dalla Mother per costruire il layer adapter cross-pianeta del Sole AI (7 settori) + retrofit del Pianeta Booking come secondo modulo conforme al Planet Module System.

Sei un executor AUTONOMO FULL. Founder è AFK · standing autorization full autonomy v2 (2026-05-16 sera). Zona grigie (naming pubblico, SoleAdapter shape, i18n keys structure) → DECIDI best technical + logga inline PR body + ADR draft. NON aspettare Mother o founder.

DB migration write AUTORIZZATO con safety rails. Le 3 tabelle che ti servono — audit_log_gdpr_art9, patient_pseudo_map, failed_webhooks — vanno CREATE direttamente via Supabase MCP apply_migration (project canonico yqhixdqipojvgzfadufo eu-central-1) seguendo safety rails:

  1. Test su create_branch ephemeral:

    • confirm_cost MCP step (expected <CHF 0.01)
    • create_branch → temp project copy
    • apply_migration sul branch · verify OK
    • Smoke test scenari (INSERT + SELECT + RLS isolation)
    • delete_branch cleanup
  2. Migration file unico in repo codice database/migrations/2026-05-16-pms-booking-tables.sql con:

    • UP script (CREATE TABLE IF NOT EXISTS × 3 + RLS policies + indexes)
    • DOWN script (DROP TABLE IF EXISTS × 3 con CASCADE solo se sicuro)
    • Commenti inline -- Why: per ogni decisione non-ovvia
  3. RLS policies obbligatorie workspace_id scoped:

    • audit_log_gdpr_art9 → SELECT solo workspace_id + role clinic_admin · INSERT solo service_role + role authenticated con session_id matching · NO UPDATE/DELETE (append-only)
    • patient_pseudo_map → SELECT solo workspace_id + role clinic_admin · INSERT/UPDATE solo service_role · NO DELETE (retention 10 anni)
    • failed_webhooks → SELECT/UPDATE solo workspace_id · INSERT solo service_role · DELETE solo dopo retry_count > max + admin role
  4. get_advisors smoke pre+post:

    • Pre: baseline lista warning
    • Apply migration su PROD yqhixdqipojvgzfadufo
    • Post: diff zero new high warning (RLS coverage · policy syntax · INDEX missing · ecc.)
  5. ADR database/migrations/README.md aggiornato + ADR-005 finalizzato + ADR-007 webhook DLQ schema

  6. Idempotency: CREATE TABLE IF NOT EXISTS · CREATE INDEX IF NOT EXISTS · CREATE POLICY IF NOT EXISTS · usa nomi unique per evitare collisione

  7. Encryption: audit_log_gdpr_art9 e patient_pseudo_map MUST be encrypted at rest (Supabase default OK · documenta in ADR)

  8. Retention policy: comment SQL -- RETENTION: 10 years (GDPR Art 9 + revFADP CH) su audit_log_gdpr_art9

Il founder ha autorizzato esecuzione autonoma sul tuo scope (packages/sector-adapters/ + packages/planet-booking/) entro i vincoli del brief.


Pre-requisiti (Mother ha già fatto)

  • feat/pms-foundation-base branch con contracts + CI + tooling configs
  • ✅ Worktree D:\Desktop\Multiverbe-child2-adapters creato
  • pnpm install completato senza errori
  • ✅ Child-1 sta lavorando in parallelo su packages/planet-core/ (zero overlap con tuo scope)

<task_spec> · 6 elementi

1. Goal

Costruire packages/sector-adapters/ (layer Sole AI cross-planet · 7 settori scaffold · 3 full implementati: ristorazione · parrucchieri · studi medici) + retrofit packages/planet-booking/ come secondo PlanetModule<BookingState> conforme. Booking deve esporre <BookingSlot /> consumabile da planet-web via event bus cross-planet (contract via @multiverbe/contracts).

2. Out-of-scope

  • packages/planet-core/ (è Child-1)
  • packages/planet-web/ (è Child-1)
  • Implementazione full degli altri 4 settori (hotel/gym/immobiliari/studi-prof · solo skeleton + 1 contract test ciascuno)
  • DB migration nuove tabelle (solo RLS review · escalate Mother se serve migration)

3. Vincoli hard

  • TypeScript strict · zero any · branded types da @multiverbe/contracts
  • Coverage sector-adapters (3 full) ≥80% line · ≥70% branch
  • Coverage planet-booking retrofit ≥70% line
  • Bundle: ogni adapter ≤15KB gz · planet-booking ≤80KB gz lazy chunk
  • A11y WCAG 2.2 AA su Booking mount + form prenotazione (axe-core 0 critical/serious)
  • Perf: Booking boot ≤180ms p95
  • Privacy: studi-medici adapter MUST flag gdpr_art9: true (dati sanitari) + audit log INSERT obbligatorio · ADR 005-medici-gdpr-art9.md
  • Conventional commits (commitlint enforced)
  • ZERO breaking change end-user su /booking route

4. Tools authorized

  • Read/Edit/Write/Glob/Grep (codebase)
  • Bash (pnpm, git, playwright)
  • Task tool (sub-agent Explore per ricerche complesse)
  • Supabase MCP: list_tables, get_advisors, create_branch + delete_branch per integration test ephemeral (confirm_cost MCP prima di branch · CHF <0.01 expected)
  • Vercel MCP (preview deploy verify)
  • Context7 MCP (Zod, vitest, Pact contract testing, Supabase)
  • Playwright MCP (smoke prenotazione end-to-end)

5. Success metric

  • ✅ 7 adapter directories scaffolded (ristorazione, parrucchieri, studi-medici full · hotel, gym, immobiliari, studi-prof skeleton)
  • pnpm --filter @multiverbe/sector-adapters test 100% contract pass · ≥80% cov sui 3 full
  • pnpm --filter @multiverbe/planet-booking test --coverage ≥70% line
  • pnpm --filter @multiverbe/planet-booking build size-limit pass (≤80KB gz)
  • ✅ Playwright smoke customer landing → book slot → confirmation zero error su 3 settori
  • ✅ Supabase get_advisors diff post-changes: zero new warning
  • ✅ Booking espone <BookingSlot /> consumabile cross-planet (test integration con stub planet-web)
  • ✅ ADR 002 finalizzato · ADR 005 (medici GDPR Art 9) scritto

6. Definition of Done

  • PR aperto su master via gh pr create (rebased post-Child-1 merge se serve)
  • gh pr merge --auto --squash --delete-branch auto-merge appena CI green
  • PR body: link a ADR + acceptance checklist tickata + screencast prenotazione + impatto MVP%
  • Self-critique sezione PR body (≥3 punti)

</task_spec>


Deliverable dettagliati

A. packages/sector-adapters/

A.1 Architettura

packages/sector-adapters/
├─ src/
│  ├─ index.ts              # public API · re-export tutti gli adapters + types
│  ├─ contract.ts           # SoleAdapter<I,O> base interface · runtime Zod validation helper
│  ├─ registry.ts           # adapter registry · lookup by (sector, capability)
│  ├─ ristorazione/         # FULL
│  │  ├─ index.ts
│  │  ├─ booking.adapter.ts # slot prenotazione tavoli · coperti · allergeni
│  │  ├─ menu.adapter.ts    # menu generation · stagionalità
│  │  ├─ schemas.ts         # Zod schemas input/output
│  │  └─ tests/
│  ├─ parrucchieri/         # FULL
│  │  ├─ index.ts
│  │  ├─ booking.adapter.ts # servizi catalogo · durata · risorse staff
│  │  ├─ schemas.ts
│  │  └─ tests/
│  ├─ studi-medici/         # FULL · GDPR Art 9
│  │  ├─ index.ts
│  │  ├─ booking.adapter.ts # prestazioni codici nazionali · audit log obbligatorio
│  │  ├─ gdpr.ts            # PII handling + Art 9 flagging
│  │  ├─ schemas.ts
│  │  └─ tests/
│  ├─ hotel/                # SKELETON · 1 contract test
│  ├─ gym/                  # SKELETON · 1 contract test
│  ├─ immobiliari/          # SKELETON · 1 contract test
│  └─ studi-prof/           # SKELETON · 1 contract test
└─ tests/
   ├─ contract.test.ts      # generic contract suite su tutti gli adapters registrati
   └─ integration.test.ts   # Supabase branch ephemeral · multi-adapter scenario

A.2 SoleAdapter<I,O> interface (in contract.ts)

import { z } from 'zod';
import type { Result, SectorId, AdapterId, PlanetContext } from '@multiverbe/contracts';

export type AdapterCapability =
  | 'booking.slot.create'
  | 'booking.slot.confirm'
  | 'booking.slot.cancel'
  | 'menu.generate'
  | 'service.catalog.list'
  // ... extensible
;

export interface AdapterError {
  readonly code: string;
  readonly message: string;
  readonly retryable: boolean;
  readonly cause?: unknown;
}

export interface SoleAdapter<TInput, TOutput> {
  readonly id: AdapterId;
  readonly sector: SectorId;
  readonly capability: AdapterCapability;
  readonly inputSchema: z.ZodSchema<TInput>;
  readonly outputSchema: z.ZodSchema<TOutput>;
  invoke(input: TInput, ctx: PlanetContext): Promise<Result<TOutput, AdapterError>>;
  readonly metadata: {
    readonly version: `${number}.${number}.${number}`;
    readonly gdpr_art9?: boolean;          // medici · true se dati sanitari
    readonly pii_fields?: ReadonlyArray<string>;
    readonly i18n_keys?: ReadonlyArray<string>; // i18n placeholder 5 langs
  };
}

export function defineAdapter<I, O>(spec: SoleAdapterSpec<I, O>): SoleAdapter<I, O> {
  // DX helper · wrap with Zod runtime validation pre/post · OTel span · audit log if gdpr_art9
  // Returns adapter conforming to interface
}

ESCALATION: contract design SoleAdapter<I,O> è zona alta complessità. Time-box 60min hard cap su contract design · escalate Opus 4.7 se non chiaro after 60min · fallback semplice (no defineAdapter helper, manual conformance).

A.3 Ristorazione adapter (full)

booking.adapter.ts — slot prenotazione tavoli:

  • Input: { workspace_id, restaurant_id, date, time, party_size, allergens?, notes? }
  • Output: { booking_id, slot_confirmed_at, table_assigned, eta_minutes_to_confirm }
  • Logic: query bookings tabella · check overbooking (max coperti slot) · INSERT booking · enqueue confirmation email
  • Eventi pubblicati su event bus: booking:slot:created, booking:slot:failed
  • i18n keys: booking.slot.create.success, booking.slot.create.overbooking, booking.slot.create.invalid_party_size

menu.adapter.ts — menu generation con AI (Anthropic Sonnet 4.6, pseudonimizzazione PII pre-call):

  • Input: { workspace_id, sector_context, season, style_preference, allergen_alternatives? }
  • Output: { menu_sections: [{ name, items: [{ name, description, allergens, price_range }] }] }
  • Pseudo: nome ristorante + indirizzo NON inviati ad Anthropic (US, fuori EU Data Boundary) · solo seed astratti
  • Output cached in sites.menu_cache (jsonb) per riuso

A.4 Parrucchieri adapter (full)

booking.adapter.ts — servizi catalogo + risorse staff:

  • Input: { workspace_id, salon_id, service_ids, preferred_staff_id?, date, time_window }
  • Output: { booking_id, slot_confirmed_at, staff_assigned, total_duration_min, total_price }
  • Logic: lookup services catalog · check staff_availability jsonb · compose timeslot · respect break_minutes
  • Eventi: booking:slot:created, booking:staff:assigned
  • i18n keys: 6 chiavi

A.5 Studi-medici adapter (full · GDPR Art 9)

booking.adapter.ts — prestazioni mediche con dati sanitari:

  • Input: { workspace_id, clinic_id, patient_codice_fiscale (PII), prestazione_code (national), date, time, urgency }
  • Output: { booking_id, slot_confirmed_at, prestazione_detail, patient_pseudonym_id }
  • Logic:
    1. Verify workspace tier ≥ Pro (Starter denied via Result.err('TIER_REQUIRED'))
    2. Compute patient_pseudonym_id (deterministic SHA-256 hash workspace_id + CF) → store mapping in patient_pseudo_map (RLS workspace + admin role only)
    3. INSERT booking con patient_pseudonym_id (no CF in tabella bookings standard)
    4. INSERT mandatory audit log audit_log_gdpr_art9 (who · when · what prestazione · patient pseudo)
    5. Email confirmation: contiene pseudo, NON CF · template medici dedicato
  • metadata.gdpr_art9 = true
  • ADR 005-medici-gdpr-art9.md documenta scelte privacy-by-design (vedi 40-adr-templates.md)

gdpr.ts:

  • pseudonymize(workspaceId: WorkspaceId, codiceFiscale: string): PatientPseudonymId
  • auditLogArt9(event: Art9Event): Promise<void> → INSERT in tabella · NON catturare CF · solo pseudo
  • Test: roundtrip pseudonimizzazione · audit log INSERT con RLS verified · NO leakage CF in logs

APPLY MIGRATION: tabelle patient_pseudo_map e audit_log_gdpr_art9 create tu in questa sessione via Supabase MCP apply_migration seguendo safety rails (vedi inizio brief § "DB migration write AUTORIZZATO"). Migration file database/migrations/2026-05-16-pms-booking-tables.sql include tutte e 3 le tabelle in singolo file (atomic) + ADR-005 + ADR-007.

A.6 Skeleton 4 settori restanti (hotel/gym/immobiliari/studi-prof)

Ogni skeleton:

  • index.ts export { sector: 'hotel'|..., adapters: [] }
  • manifest.ts con sector metadata (displayName 5 langs · capabilities placeholder)
  • 1 contract test in tests/ che verifica adapter conformance interface (anche se array empty)
  • README.md con TODO list per implementation Q3 2026

A.7 Tests

  • tests/contract.test.ts: generic suite che itera su tutti gli adapter registrati · 10 contract test per adapter
  • tests/integration.test.ts: scenario multi-adapter via Supabase branch ephemeral:
    • Setup create_branch MCP (verify confirm_cost <CHF 0.01)
    • Run 5 booking scenarios cross-sector
    • Verify RLS isolation + audit log art9 + email enqueue
    • Teardown delete_branch MCP
  • Snapshot test su seed data adapter output

B. packages/planet-booking/ retrofit

Code esistente in frontend/multiverbe-app/src/booking/ (audit primi 10min · path possono variare).

B.1 Mapping codice esistente → PMS

Code esistente PlanetModule slot
booking/components/BookingFlow.tsx module.mount() ritorna questo
booking/components/BookingSlot.tsx export pubblico cross-planet (consumabile da planet-web)
booking/lib/state.ts BookingState tipo
api/bookings/* manifest routes + healthcheck
Form submission webhook OTel span + idempotency key (header X-Idempotency-Key)

B.2 Cross-planet contract: <BookingSlot /> embeddable

planet-web deve poter renderizzare <BookingSlot /> in landing PMI · contratto:

  • Props: { workspaceId, sectorId, mode: 'inline' | 'modal' | 'sheet', onConfirm?: (booking) => void }
  • Internamente: cerca adapter via sectorAdaptersRegistry.find({ sector, capability: 'booking.slot.create' }) · fallback generic adapter se settore unknown
  • Event bus publish booking:slot:created consumable da planet-web (analytics + tracking)
  • Forms: i18n-ready · accessibility WCAG 2.2 AA · keyboard nav · screen reader announcements

B.3 Form submissions + webhooks

  • INSERT form_submissions con idempotency_key UNIQUE
  • Webhook trigger Brevo/Resend per confirmation email (template settore-specifico via adapter metadata.i18n_keys)
  • OTel span booking.webhook.brevo con duration + status
  • Error retry: exponential backoff max 3 retry · DLQ via failed_webhooks table (escalate Mother se non esiste)

B.4 Manifest planet-booking

export const manifest: PlanetManifest = {
  id: 'planet-booking' as PlanetId,
  kind: 'booking',
  version: '1.0.0',
  displayName: { it: 'Pianeta Booking', de: 'Booking-Planet', fr: 'Planète Réservations', en: 'Booking Planet', es: 'Planeta Reservas' },
  description: { it: 'Prenotazioni settore-aware', /* ... */ },
  icon: { kind: 'lucide', name: 'Calendar' },
  routes: [
    { path: '/booking', component: 'BookingDashboard', prefetch: true },
    { path: '/booking/:bookingId', component: 'BookingDetail', prefetch: false },
    { path: '/booking/settings', component: 'BookingSettings', prefetch: false },
  ],
  permissions: ['booking:read', 'booking:write', 'booking:admin'],
  rlsTables: ['bookings', 'services', 'staff_availability', 'form_submissions'],
  telemetry: {
    spans: ['booking.slot.create', 'booking.slot.confirm', 'booking.webhook'],
    events: { slotCreated: SlotCreatedSchema, slotConfirmed: SlotConfirmedSchema, slotCancelled: SlotCancelledSchema },
  },
  featureFlags: [
    { key: 'booking.adapter.medici', default: false, description: 'Enable studi-medici adapter (GDPR Art 9 audit)' },
    { key: 'booking.webhook.dlq', default: true, description: 'Dead letter queue for failed webhooks' },
  ],
  dependencies: [], // soft: planet-web consume BookingSlot ma non hard dep
  stateSchemaVersion: 1,
  migrations: [],
  bundleBudgetKb: 80,
  a11yProfile: 'wcag-2.2-aa',
};

B.5 Integration test con planet-web (stub Mother fase-0 esistente):

test('BookingSlot embeddable in planet-web', async () => {
  const webStub = createPlanetWebStub();
  const bookingModule = await loadPlanet('booking');
  webStub.mount({ children: <BookingSlot workspaceId={ws} sectorId="ristorazione" mode="inline" /> });
  // verify render OK, event bus publishes booking:slot:created on submit
});

C. RLS audit Supabase MCP

A inizio + fine sub-sessione:

  1. get_advisors smoke baseline (lista warning attuali)
  2. Implement changes
  3. get_advisors re-run · diff: zero new warning (hard gate)
  4. Documenta in PR body + <security_check> output

Acceptance criteria (PR-blocking)

[ ] pnpm typecheck                              → exit 0 (zero any)
[ ] pnpm lint                                   → zero new warning
[ ] pnpm --filter @multiverbe/sector-adapters test            → 100% contract pass
[ ] pnpm --filter @multiverbe/sector-adapters test --coverage → ≥80% line (sui 3 full · skeleton excluded)
[ ] pnpm --filter @multiverbe/planet-booking test --coverage  → ≥70% line
[ ] pnpm --filter @multiverbe/sector-adapters build           → ogni adapter ≤15KB gz
[ ] pnpm --filter @multiverbe/planet-booking build            → size-limit pass (≤80KB gz)
[ ] pnpm dlx madge --circular packages/sector-adapters packages/planet-booking → 0 circular
[ ] Playwright smoke 3 settori (ristorazione + parrucchieri + medici) end-to-end → zero error
[ ] axe-core scan su /booking + form prenotazione             → 0 critical/serious
[ ] Supabase get_advisors diff post-changes                   → 0 new warning
[ ] Integration test multi-adapter via Supabase branch ephemeral → pass + branch deleted
[ ] BookingSlot consumable da planet-web stub (cross-planet contract)   → pass
[ ] Studi-medici adapter: GDPR Art 9 flag + audit log + pseudonimizzazione tests → pass
[ ] ADR 002 finalizzato · ADR 005 (medici Art 9) scritto       → done
[ ] PR body: scope + impatto MVP% + screencast prenotazione + self-critique 3+ punti → present
[ ] Commitlint conventional commits enforced                   → pass

Time budget Child-2 (6h)

Slot Task Durata
0:00–0:30 Read brief + audit codebase esistente Booking + lookup Supabase tabelle relative 30min
0:30–1:30 sector-adapters/ contract.ts + registry + ristorazione adapter full + tests 60min
1:30–2:30 parrucchieri adapter full + tests 60min
2:30–3:30 studi-medici adapter + GDPR Art 9 logic + tests · ADR 005 60min
3:30–4:00 4 skeleton settori (hotel/gym/immobiliari/studi-prof) + contract test cad 30min
4:00–5:00 planet-booking/ retrofit + manifest + cross-planet contract BookingSlot 60min
5:00–5:30 Integration test Supabase branch ephemeral + RLS audit 30min
5:30–6:00 PR open + self-critique + ADR 002 finalize 30min

Buffer 0 minutes. Slip ≥30min → ESCALATE Mother.


Decision matrix · autonomous mode

Situazione Azione
Tabelle DB mancanti (audit_log_gdpr_art9, patient_pseudo_map, failed_webhooks) PROCEDI con migration · safety rails (test branch ephemeral + UP/DOWN + RLS + get_advisors diff + ADR + idempotency + retention 10y) · apply via MCP apply_migration su project canonico yqhixdqipojvgzfadufo · proceed
GDPR Art 9 design uncertainty DECIDI privacy-by-design strict (pseudonimizzazione + audit log + flag default OFF + tier Pro+) · ADR-005 finalizza · proceed
Adapter contract design dubbio Time-box 60min · poi go con shape semplice (no defineAdapter helper, manual conformance) · ADR documenta trade-off · proceed
Cross-planet contract BookingSlot shape DECIDI shape { workspaceId, sectorId, mode, onConfirm? } + props minimal · ADR documenta · Child-1 si adatta in retrofit Web
Scope creep Slip ≥30min → 4 settori skeleton invece di 7 · action_item BOS per resto · PROCEED
Supabase branch cost dubbio confirm_cost MCP automatic step · expected <CHF 0.01
CI fail post-PR Fix forward stesso PR · NON aprire nuovo · proceed
Naming i18n keys DECIDI struttura <sector>.<capability>.<event> lowercase dot-separated · ADR · proceed

Self-improvement loop Child-2

A fine sub-sessione:

  1. Self-critique PR body ≥3 punti
  2. Pattern learned snippet per Mother
  3. Wow candidate ≥1 idea
  4. Handover 1 riga: cosa fragile/da-rivedere · cosa rock-solid

START COMMAND Child-2 (autonomous)

  1. Leggi questo brief intero
  2. Audit codebase: cd D:\Desktop\Multiverbe-child2-adapters · pnpm install · pnpm test -r baseline · git log --oneline -20 · Supabase MCP list_tables per verificare tabelle (audit_log_gdpr_art9, patient_pseudo_map, failed_webhooks)
  3. ACK 3 righe a Mother (1 task_spec · 1 risk · 1 confidence%) · NO domande · NO wait · se tabelle DB mancano → annota + STUB + procedi
  4. START SUBITO time-boxed seguendo § Time budget Child-2
  5. PR open: gh pr create --base master --title "feat(planet-system): sector adapters + Booking retrofit" --body "$(cat PR_BODY.md)"
  6. Auto-merge: gh pr merge <PR_NUM> --auto --squash --delete-branch
  7. Ritorna result alla Mother (Task tool result include: PR URL, merge status, acceptance checks summary, self-critique, decisioni zona grigia loggate, tabelle DB mancanti se applicable)