HANDOFF — Multiverbe · E2E verde + Igiene DB + Cleanup deprecati + No-show simplify

Preparato 2026-06-06 (sessione reverent-hermann-66aa02, contesto pieno). Esegui in sessione fresca, repo codice D:\Desktop\Multiverbe. Ordine e DoD sotto. Qualità BigTech, gate-ready.


0 · CHI SEI / MISSIONE

Sei una sessione Claude Code autonoma sul prodotto MULTIVERBE (BOS per PMI svizzere). Repo D:\Desktop\Multiverbe (lavora in un worktree dedicato). Supabase yqhixdqipojvgzfadufo (eu-central-1). Deploy Vercel su merge in master. Gate vicino (10 Giu / sprint 15 Giu): chiudi buchi e igiene con qualità estrema, NON costruire feature nuove. Procedi in autonomia: branch → PR → CI verde → squash-merge → verifica live. Parla al founder solo per vere decisioni (schema prod rischioso, pricing/canon, sicurezza, hard-to-revert).

Il founder ha scelto in questa sessione: fare 2+3+4 (E2E verde + Igiene DB + Cleanup deprecati) + No-show #287 = "nascondi/semplifica di più", tutto in ordine e in estrema qualità.

1 · OPERATING CHARTER (sintesi — vale sempre)

Leggi le memorie operating_charter, feedback_no_hardcoding_quality, project_ci_vercel_cost_2026_06_06, feedback_notification_architecture_hub_crm, project_supabase_advisor_hardening_2026_06_03 (metodo dry-run RLS), feedback_do_not_touch_3d_planets. DoD (un task è "fatto" SOLO se): funzionante + verificato dal vivo (test o passi manuali dichiarati) + testato dove ha senso (test/doc nello stesso commit) + piccolo/coeso (no refactor mescolati, YAGNI) + zero hardcoded interno customer-facing + sicuro multi-tenant (filtra workspace_id in ogni query/cache/storage, ownership al data-layer, RLS non basta da sola) + scalabile/osservabile + niente regressioni + fix alla radice (no declassamenti) + merge solo a CI verde + advisor 0 nuovi finding. Processo: plan-first per lavori non banali; proponi la scelta migliore con trade-off; verifica lo stato reale (DB/prod/CI) prima di agire; chiedi prima solo per schema prod rischioso / pricing-canon / sicurezza / hard-to-revert.

2 · COST-MODE CI/INFRA (attivo, additivo ai gate)

Pre-revenue: ogni minuto CI e ogni build Vercel costa. Verifica in LOCALE prima di pushare (la PR nasce verde, niente catene di fix-commit). Batch in pochi commit coesi, 1 PR per unità, squash-merge. No commit usa-e-getta. Niente nuovi cron. I gate NON si toccano (typecheck+build+lint+test+security restano). GitHub budget Actions $40+stop (NON rimetterlo a $0 = blocca tutta la CI). Vercel skip-deployments ON + cap $25.

3 · METODO DI VERIFICA (questo repo)

  • api/ NON è typecheckato da tsc (tsconfig include solo src/). Per i file api/: node <root>/node_modules/.bin/esbuild <file> --bundle --platform=node --format=esm --packages=external (sintassi) + vitest col binario root e config usa-e-getta css.postcss:{plugins:[]} (test puri, vedi memoria reference_local_vitest_worktree_technique) + node scripts/check-api-esm-imports.mjs (import relativi api/ DEVONO avere .js).
  • Lint è il gate vero per api/: eslint src/ api/ --max-warnings=560 (cap ASSOLUTO, ~554 oggi). Evita security/detect-object-injection (niente obj[varKey]: usa switch o Map.get da Record via Object.entries). Niente eslint-disable morti.
  • Frontend: pnpm --filter multiverbe-dashboard build = tsc && vite build (è ciò che builda Vercel; usalo come typecheck affidabile anche se Actions è giù).
  • Schema: dry-run DO-block + RAISE 'DRYRUN_OK' (forza rollback, testa su ruoli/dati reali) → apply_migration MCP → advisor 0 nuovi WARN → se cambia lo schema-dati rigenera src/types/supabase.ts (hand-edit minimale multi-line; l'output MCP è single-line). Indici/policy non cambiano i types.
  • Frontend dietro auth = QA founder (no Playwright). Tu verifichi backend + smoke endpoint (es. POST senza token → 401).
  • Web: per stack di ultimo livello (Playwright 2026, Supabase RLS, Vercel) valida sul web le best practice correnti prima di decidere.

4 · STATO GIÀ VERIFICATO 2026-06-06 (NON rifare)

  • CI master VERDE: frontend-ci.yml success su HEAD. I merge --admin #278/#287/#292 (durante il blocco billing) non hanno lasciato regressioni.
  • Login fresco in PROD è SANO (verificato con Playwright su https://multiverbe.app): cookie→email→password→bottone "Apri Multiverbe" cliccabile in <500ms, form stabile (id useId invariato, bbox ferme), zero overlay che intercetta, networkidle <1s. Il form è MAIN.mv-auth-form-col con textbox "Email" + input[type=password] + button "Apri Multiverbe".
  • Advisor SECURITY: 0 ERROR, 0 WARN, 2 INFO rls_enabled_no_policy (external_api_usage, idempotency_keys) = fail-closed accettati (RLS ON + 0 policy → deny-by-default; tabelle di servizio). Eventuale micro-hardening: revocare i grant ridondanti anon,authenticated su quelle 2 tabelle (non bloccante).
  • Advisor PERFORMANCE: 0 ERROR, 13 WARN (dettagli in TASK 1), 145 INFO (109 unused_index + 35 unindexed_FK = normale pre-traffico, non toccare in massa).

I 4 TASK — in ordine

▶ TASK 1 — Igiene DB pre-scala (advisor performance → 0 WARN) · PR "chore(db): advisor hygiene"

Perché: porta l'advisor a 0 WARN, RLS più pulita/scalabile. Verificabile end-to-end dal vivo. Tocca RLS → procedi col metodo dry-run + verifica accessi prima/dopo (rischio cross-tenant se sbagli la semantica).

1a · duplicate_index (2) — quick win, ma VERIFICA prima di droppare

  • public.entity_interactions: {idx_entity_interactions_ws, idx_entity_interactions_ws_created} segnalati identici.
  • public.tier_caps: {tier_caps_tier_metric_key, uq_tier_caps_tier_metric} segnalati identici. ⚠️ Verifica le definizioni reali (select indexdef from pg_indexes where indexname in (...)) PRIMA: i nomi suggeriscono colonne diverse (_ws vs _ws_created). Se NON sono davvero identici, NON droppare (l'advisor confronta le colonne ma controlla). Per tier_caps: uno è probabilmente un UNIQUE constraint (uq_…) e uno un index — droppa l'index, mai il constraint (servirebbe drop index, non toccare il constraint che garantisce l'unicità). Migration additiva/reversibile.

1b · multiple_permissive_policies (11) — consolidamento RLS

Due gruppi, due pattern di fix:

  • 6 tabelle site_* (site_assets, site_block_translations, site_blocks, site_pages, site_subdomains, sites) — role anon, action SELECT, policy {*_public_read_published, *_select}. Il *_select (owner/authenticated) si applica anche ad anon → ridondanza. Fix: restringere *_select a TO authenticated (gli owner loggati), lasciando *_public_read_published come unica policy anon. Verifica: anon legge SOLO i siti published, NON i draft/non-pubblicati; l'owner loggato continua a leggere i propri.
  • 5 tabelle Sales/team (deals, sales_commissions_rules, sales_quotas, sales_territories, team_member_planet_access) — role authenticated, action SELECT, policy {*_modify, *_select}. La *_modify è probabilmente FOR ALL → include SELECT, si sovrappone a *_select. Fix: cambiare *_modify da FOR ALL a FOR INSERT/UPDATE/DELETE (o split), lasciando *_select per la lettura. Verifica: un utente legge solo i record del proprio workspace_id; le scritture restano protette. Metodo (memoria project_supabase_advisor_hardening_2026_06_03): per ogni tabella ispeziona le policy reali (pg_policies), scrivi la migration, dry-run in DO-block con verifica set role anon/authenticated + request.jwt.claims simulati su dati reali (un sito published vs draft; un deal di workspace A vs B), poi apply_migration. Usa (SELECT auth.uid()) wrap per initplan caching (skill multiverbe-supabase-rls-patterns). DoD TASK 1: advisor performance recheck → quei 13 WARN spariti, 0 nuovi finding security/perf; accessi anon/authenticated invariati nella semantica (verificati prima/dopo); migration nel repo database/migrations/; nessun cambio ai types. CI verde.

▶ TASK 2 — No-show #287 "nascondi/semplifica di più" (decisione founder) · PR "feat(booking): de-emphasize no-show fee UI"

File: UI in frontend/multiverbe-app/src/features/booking/dashboard/BookingDashboard.tsx (bottone "Applica penale CHF X"); endpoint api/booking/no-show-fee.ts (NON toccare la logica/idempotenza). Helper i18n in api/booking/_lib/i18n.ts. Cosa fare: rendere il no-show meno prominente nella dashboard. Oggi compare se il titolare ha no_show_fee_chf>0 (già gated). "Semplifica di più" = es. spostarlo dietro un dettaglio/menu "azioni avanzate" della prenotazione no_show, o ridurlo a voce discreta non in primo piano; togliere enfasi visiva. Non rimuovere endpoint/colonne (bookings.no_show_fee_applied_chf/at) — solo UI. Verifica con il founder il livello di "nascondere" (QA visiva sua). DoD: tsc && vite build verde; endpoint intatto (smoke POST /api/booking/no-show-fee senza token → 401); UI verificata dal founder; copy eventuale via skill multiverbe-copy (it + de/fr/en se tocchi stringhe utente). Aggiorna memoria feedback_notification_architecture_hub_crm (no-show ora "semplificata" non solo gated).

▶ TASK 3 — Cleanup deprecati: edge ai-copilot-* (parte SICURA) · PR "chore: remove deprecated ai-copilot edge functions"

Contesto: 8 edge supabase/functions/ai-copilot-* (-chat,-scan,-pulse,-goals,-weekly,-monthly,-yearly,-competitors) + _shared/{sovereign,sole-identity,ai-client}.ts. NON sono deployate (list_edge_functions MCP = vuoto) → sostituite dal routing sovrano in api/_lib/sovereign. Sono codice morto in repo. ⚠️ Cautele prima di rimuovere:

  1. Verifica che NESSUN cron/webhook/Inngest attivo le invochi (grep ai-copilot in api/, .github/workflows, vercel.json, supabase cron). Il hook frontend src/hooks/useAIChat.ts è ATTIVO e usa il routing nuovo → NON è questo, non toccarlo.
  2. scripts/check-sole-identity-parity.mjs confronta l'identità Sole api/_lib ↔ edge _shared/sole-identity.ts. Se rimuovi le edge, aggiorna o rimuovi quello script + il suo step in CI (frontend-ci/planet-ci) per non lasciarlo rotto.
  3. _shared/* usati SOLO dalle edge → rimuovibili con esse; se referenziati altrove, tieni.
  4. docs/plans/2026-02-*ai-copilot* + maind-brain-* = doc storici pre-canon → archivia/rimuovi (basso rischio). DoD: build verde, nessun import rotto, parity-script e CI coerenti (verde), zero riferimenti orfani. Additivo/reversibile (git).

▶ TASK 4 — E2E smoke verde affidabile · PR "test(e2e): robust login flow" — richiede input founder per validare

Diagnosi già fatta (NON rifare): il login in prod è SANO. I 4 e2e (tests/e2e/{login-smoke,crm-add-contact,booking-create,stripe-checkout}.spec.ts) falliscono solo in ambiente CI (runner headless lento), non in prodotto. Sintomi: fill/click vanno in "Test timeout 30000ms" (budget globale del test, non l'actionTimeout) → qualcosa prima consuma i 30s. Causa probabile: page.goto('/') usa il default waitUntil:'load' che aspetta TUTTI gli asset (scena 3D pesante) → su runner CI lento mangia il test-timeout; l'ultima azione (password fill in crm, click in login-smoke) sfora. Dal vivo (rete veloce) non si riproduce. Fix raccomandato (best-practice Playwright 2026 — valida sul web):

  1. page.goto('/', { waitUntil: 'domcontentloaded' }) (non 'load') + web-first wait sul form login prima di interagire.
  2. test.setTimeout(60_000) (o timeout in config) per assorbire la lentezza CI.
  3. Cookie banner robusto: è un <div role=dialog> non-modale, ancorato in fondo (NON copre il form). Il check isVisible() immediato è race-y. Meglio: dismissalo deterministicamente (click su "Va bene così" con waitFor breve, oppure pre-seed localStorage del consenso prima del goto).
  4. Verifica che playwright.config.ts navigationTimeout/actionTimeout siano coerenti col nuovo waitUntil. VINCOLO: per riprodurre/validare servono CLAUDE_DEMO_EMAIL/CLAUDE_DEMO_PASSWORD (GH secrets, non in chiaro). Chiedi al founder: (a) le credenziali demo per girare pnpm --filter multiverbe-dashboard test:e2e in locale headless contro prod, OPPURE (b) l'ok a pushare il fix + lanciare UN workflow_dispatch di e2e-smoke.yml per validare (1 run, cost-mode ok). Non pushare fix speculativi a raffica. DoD: i 4 e2e verdi in CI (workflow_dispatch su prod), login+nav confermati; nessun falso-rosso residuo.

⏸ (OPZIONALE, RISCHIOSO — NON nel giro "sicuro") — rename opera legacy

8 file usano il naming MAIND opera: src/types/planets.ts, src/store/index.ts, src/lib/cloudflare-api.ts, src/components/layout/{SectionTabBar,PlanetSidebar,PlanetScene,DesktopSidebar}.tsx, src/components/dashboard/InsightsFeed.tsx. ⚠️ PlanetScene.tsx = scena 3D = territorio founder (memoria feedback_do_not_touch_3d_planets, chiedi prima). entity_interactions.planet CHECK rifiuta opera (accetta crm|booking|…) → c'è una mappatura; il refactor è classificato post-MVP in CLAUDE.md. Raccomandazione: NON farlo in questo giro (o solo con ok esplicito founder e SENZA toccare geometria/animazione 3D). Mappare operacrm/booking con cura, zero regressioni UI.


5 · PRIMO PASSO CONCRETO

  1. Conferma CI master verde (gh run list --branch master --workflow frontend-ci.yml --limit 1).
  2. Apri TASK 1 (Igiene DB): introspeziona pg_indexes + pg_policies per le 13 tabelle/indici elencati, costruisci la migration, dry-run con verifica accessi anon/authenticated su dati reali, applica, advisor recheck → 0 WARN. Una PR coesa.
  3. Procedi TASK 2 → 3, ognuno PR coesa verificata in locale → CI verde → squash-merge → verifica live.
  4. TASK 4 (E2E): chiedi subito al founder le credenziali demo o l'ok al workflow_dispatch, così non resti bloccato a fine.
  5. Chiudi con blocco 🔴 DA FARE + aggiorna BOS (status.json: action_items, decisione no-show "semplificata") + memorie + push BOS.

6 · DoD GLOBALE

Ogni PR: verificata in locale (esbuild/vitest/check-esm/eslint≤560 per api/; tsc && vite build per frontend) → nasce verde → CI verde → squash-merge → verifica live (smoke endpoint/advisor) → niente regressioni → advisor 0 nuovi. Non perdere nulla: commit a fine + 🔴 DA FARE + BOS aggiornato e pushato.