🪐 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-adaptersBranch:feat/pms-adapters-booking-retrofitdafeat/pms-foundation-baseParent: Mother session (vedi00-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:
-
Test su
create_branchephemeral:confirm_costMCP step (expected <CHF 0.01)create_branch→ temp project copyapply_migrationsul branch · verify OK- Smoke test scenari (INSERT + SELECT + RLS isolation)
delete_branchcleanup
-
Migration file unico in repo codice
database/migrations/2026-05-16-pms-booking-tables.sqlcon:- 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
- UP script (
-
RLS policies obbligatorie workspace_id scoped:
audit_log_gdpr_art9→ SELECT solo workspace_id + roleclinic_admin· INSERT soloservice_role+ role authenticated con session_id matching · NO UPDATE/DELETE (append-only)patient_pseudo_map→ SELECT solo workspace_id + roleclinic_admin· INSERT/UPDATE soloservice_role· NO DELETE (retention 10 anni)failed_webhooks→ SELECT/UPDATE solo workspace_id · INSERT soloservice_role· DELETE solo dopo retry_count > max + admin role
-
get_advisorssmoke pre+post:- Pre: baseline lista warning
- Apply migration su PROD
yqhixdqipojvgzfadufo - Post: diff zero new high warning (RLS coverage · policy syntax · INDEX missing · ecc.)
-
ADR
database/migrations/README.mdaggiornato + ADR-005 finalizzato + ADR-007 webhook DLQ schema -
Idempotency:
CREATE TABLE IF NOT EXISTS·CREATE INDEX IF NOT EXISTS·CREATE POLICY IF NOT EXISTS· usa nomi unique per evitare collisione -
Encryption:
audit_log_gdpr_art9epatient_pseudo_mapMUST be encrypted at rest (Supabase default OK · documenta in ADR) -
Retention policy: comment SQL
-- RETENTION: 10 years (GDPR Art 9 + revFADP CH)suaudit_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-basebranch con contracts + CI + tooling configs - ✅ Worktree
D:\Desktop\Multiverbe-child2-adapterscreato - ✅
pnpm installcompletato 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-bookingretrofit ≥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 · ADR005-medici-gdpr-art9.md - Conventional commits (commitlint enforced)
- ZERO breaking change end-user su
/bookingroute
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_branchper integration test ephemeral (confirm_costMCP 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-medicifull ·hotel,gym,immobiliari,studi-profskeleton) - ✅
pnpm --filter @multiverbe/sector-adapters test100% contract pass · ≥80% cov sui 3 full - ✅
pnpm --filter @multiverbe/planet-booking test --coverage≥70% line - ✅
pnpm --filter @multiverbe/planet-booking buildsize-limit pass (≤80KB gz) - ✅ Playwright smoke
customer landing → book slot → confirmationzero error su 3 settori - ✅ Supabase
get_advisorsdiff post-changes: zero new warning - ✅ Booking espone
<BookingSlot />consumabile cross-planet (test integration con stubplanet-web) - ✅ ADR 002 finalizzato · ADR 005 (medici GDPR Art 9) scritto
6. Definition of Done
- PR aperto su
masterviagh pr create(rebased post-Child-1 merge se serve) gh pr merge --auto --squash --delete-branchauto-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
bookingstabella · 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
servicescatalog · checkstaff_availabilityjsonb · 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:
- Verify workspace tier ≥ Pro (Starter denied via
Result.err('TIER_REQUIRED')) - Compute
patient_pseudonym_id(deterministic SHA-256 hash workspace_id + CF) → store mapping inpatient_pseudo_map(RLS workspace + admin role only) - INSERT booking con
patient_pseudonym_id(no CF in tabellabookingsstandard) - INSERT mandatory audit log
audit_log_gdpr_art9(who · when · what prestazione · patient pseudo) - Email confirmation: contiene pseudo, NON CF · template medici dedicato
- Verify workspace tier ≥ Pro (Starter denied via
metadata.gdpr_art9 = true- ADR
005-medici-gdpr-art9.mddocumenta scelte privacy-by-design (vedi40-adr-templates.md)
gdpr.ts:
pseudonymize(workspaceId: WorkspaceId, codiceFiscale: string): PatientPseudonymIdauditLogArt9(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.tsexport{ sector: 'hotel'|..., adapters: [] }manifest.tscon sector metadata (displayName 5 langs · capabilities placeholder)- 1 contract test in
tests/che verifica adapter conformance interface (anche se array empty) README.mdcon 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 adaptertests/integration.test.ts: scenario multi-adapter via Supabase branch ephemeral:- Setup
create_branchMCP (verifyconfirm_cost<CHF 0.01) - Run 5 booking scenarios cross-sector
- Verify RLS isolation + audit log art9 + email enqueue
- Teardown
delete_branchMCP
- Setup
- 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:createdconsumable 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_submissionsconidempotency_keyUNIQUE - Webhook trigger Brevo/Resend per confirmation email (template settore-specifico via adapter
metadata.i18n_keys) - OTel span
booking.webhook.brevocon duration + status - Error retry: exponential backoff max 3 retry · DLQ via
failed_webhookstable (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:
get_advisorssmoke baseline (lista warning attuali)- Implement changes
get_advisorsre-run · diff: zero new warning (hard gate)- 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:
- Self-critique PR body ≥3 punti
- Pattern learned snippet per Mother
- Wow candidate ≥1 idea
- Handover 1 riga: cosa fragile/da-rivedere · cosa rock-solid
START COMMAND Child-2 (autonomous)
- Leggi questo brief intero
- Audit codebase:
cd D:\Desktop\Multiverbe-child2-adapters·pnpm install·pnpm test -rbaseline ·git log --oneline -20· Supabase MCPlist_tablesper verificare tabelle (audit_log_gdpr_art9, patient_pseudo_map, failed_webhooks)- ACK 3 righe a Mother (1 task_spec · 1 risk · 1 confidence%) · NO domande · NO wait · se tabelle DB mancano → annota + STUB + procedi
- START SUBITO time-boxed seguendo § Time budget Child-2
- PR open:
gh pr create --base master --title "feat(planet-system): sector adapters + Booking retrofit" --body "$(cat PR_BODY.md)"- Auto-merge:
gh pr merge <PR_NUM> --auto --squash --delete-branch- 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)