🪐 CHILD-1 BRIEF · Planet Core + Web Retrofit

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

Modello: Sonnet 4.6 high (escalate Opus 4.7 su event bus design) Worktree: D:\Desktop\Multiverbe-child1-pms-core Branch: feat/pms-core-web-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-1, sub-sessione spawned dalla Mother per costruire il core del Planet Module System (PMS) + retrofit del Pianeta Web come primo modulo conforme.

Sei un executor AUTONOMO FULL. Founder è AFK · standing autorization full autonomy v2 (2026-05-16 sera). Zona grigie (naming pubblico, sezioni W-010, signature event-bus) → DECIDI best technical + logga inline nel PR body + ADR draft. NON aspettare Mother o founder.

DB migration write AUTORIZZATO con safety rails (vedi 00-mother-brief.md). Se per feature_flag_audit table (audit log flag flip) serve migration → applica via Supabase MCP apply_migration (project yqhixdqipojvgzfadufo eu-central-1) seguendo safety rails:

  1. Test su create_branch ephemeral · verify ok · delete_branch
  2. Migration file database/migrations/2026-05-16-feature-flag-audit.sql UP + DOWN reversibile
  3. RLS policy workspace_id scoped + role admin only per SELECT
  4. get_advisors smoke pre+post (diff 0 new high warning)
  5. ADR 006-feature-flag-audit-log.md
  6. Idempotency: CREATE TABLE IF NOT EXISTS feature_flag_audit (...)

Il founder ha autorizzato esecuzione autonoma sul tuo scope (packages/planet-core/ + packages/planet-testkit/ + packages/planet-web/) entro i vincoli del brief.


Pre-requisiti (Mother ha già fatto)

  • feat/pms-foundation-base branch con:
    • packages/contracts/src/planet.ts (TS contracts)
    • .github/workflows/planet-system-ci.yml
    • pnpm-workspace.yaml aggiornato con packages/*
    • Tooling configs: commitlint, husky, lint-staged, size-limit, stryker, changesets
    • docs/adr/001-planet-module-system.md (seed · da finalizzare)
  • ✅ Worktree D:\Desktop\Multiverbe-child1-pms-core creato
  • pnpm install completato senza errori

<task_spec> · 6 elementi

1. Goal

Costruire packages/planet-core/ (registry · loader · event bus · telemetry · feature flag · state migrator · healthcheck) + packages/planet-testkit/ (contract test harness condiviso) + retrofit packages/planet-web/ come primo PlanetModule<WebState> conforme. Risolvere come side-effect i 3 gap del Wow Audit (W-002 keyframes morti · W-010 sezioni render null · W-011 screenshot stub).

2. Out-of-scope

  • packages/sector-adapters/ (è Child-2)
  • packages/planet-booking/ (è Child-2)
  • Nuove feature funzionali Web (solo refactor strutturale + W-002/010/011 fix)
  • DB migration (solo tipi + RLS review · escalate Mother se serve migration)

3. Vincoli hard

  • TypeScript strict · zero any · branded types da @multiverbe/contracts
  • Coverage planet-core ≥80% line · ≥70% branch · mutation ≥60% (Stryker)
  • Coverage planet-web retrofit ≥70% line
  • Bundle: planet-core ≤25KB gz · planet-web ≤80KB gz lazy chunk
  • A11y WCAG 2.2 AA su Web mount (axe-core 0 critical/serious)
  • Perf: planet-web boot ≤180ms p95 (Lighthouse Vercel preview)
  • Conventional commits (commitlint enforced)
  • ZERO breaking change end-user su /web route (Playwright smoke green pre+post)

4. Tools authorized

  • Read/Edit/Write/Glob/Grep (codebase)
  • Bash (pnpm, git, playwright, lighthouse-ci)
  • Task tool (sub-agent Explore se servono ricerche complesse)
  • Supabase MCP read-only (list_tables, get_advisors smoke su Web RLS) · NO write
  • Vercel MCP (preview deploy verify)
  • Context7 MCP (docs lookup React 19, R3F v9, vitest, stryker)
  • Playwright MCP (snapshot/click/network smoke)

5. Success metric

  • pnpm --filter @multiverbe/planet-core test --coverage ≥80% line · ≥70% branch
  • pnpm --filter @multiverbe/planet-core test:mutation ≥60%
  • pnpm --filter @multiverbe/planet-web test --coverage ≥70% line
  • pnpm --filter @multiverbe/planet-web build size-limit pass (≤80KB gz)
  • ✅ Playwright smoke signup → /web → create site → preview zero error
  • ✅ W-002, W-010, W-011 risolti (vedi §"Side-effect fixes" sotto)
  • ✅ Scaffold script scripts/scaffold-planet.ts funziona pnpm run scaffold:planet <kind> <60s
  • ✅ ADR 001 e 003 finalizzati con esempi reali

6. Definition of Done

  • PR aperto su master via gh pr create con body completo (vedi sotto)
  • gh pr merge --auto --squash --delete-branch auto-merge appena CI green
  • PR body: link a ADR + acceptance checklist tickata + screenshot Vercel deploy + impatto MVP%
  • Self-critique sezione PR body "what I'd improve next iteration" (≥3 punti)

</task_spec>


Deliverable dettagliati

A. packages/planet-core/

A.1 src/registry.ts — singleton planet registry

  • register(module: PlanetModule<unknown>): Result<void, RegistryError>
  • unregister(id: PlanetId): Promise<void>
  • get(id: PlanetId): PlanetModule<unknown> | null
  • list(): ReadonlyArray<PlanetMeta>
  • boot(deps: PlanetId[]): Promise<BootReport> — risolve manifest.dependencies con topological sort · cycle detection (throw CyclicDependencyError)
  • Lifecycle hooks: onBeforeBoot, onAfterBoot, onError
  • Concurrency safe: boot multipli idempotenti (debounce 50ms)

A.2 src/loader.ts — dynamic lazy loader

  • loadPlanet(kind: PlanetKind): Promise<PlanetModule<unknown>> — code-splitting via dynamic import
  • Prefetch hint via manifest routes[].prefetch: boolean
  • Cache layer: planet loaded resta in memoria fino unmount esplicito
  • Error boundary: load fail → fallback module con manifest.kind + healthcheck red

A.3 src/event-bus.ts — typed pub/sub

  • EventBus<TEventMap> con publish(event, payload), subscribe(event, handler), unsubscribe(...)
  • Backpressure: queue max 1000 · drop-oldest config
  • OTel span propagation: ogni publish crea/continua span attivo
  • Cross-planet pattern: event namespace <planet-id>:<event-name> enforced
  • DevTools hook: globalThis.__MULTIVERBE_BUS__ in non-prod

ESCALATION: se hai dubbi su semantica concurrency/backpressure/error propagation → escalate alla Mother. Questo è il punto più delicato del package. Time-box 90min hard cap su event-bus · se sfori → fallback EventEmitter typed semplice + ADR-005 deferring.

A.4 src/telemetry.ts — OTel-compatible

  • TelemetrySink interface · default impl OTel SDK web (@opentelemetry/sdk-trace-web)
  • Perf marks: planet:{kind}:init, planet:{kind}:mount, planet:{kind}:first-interaction
  • Structured logger: logger.info({ planet, workspace_id, op }, "msg") JSON output
  • PII filter list: email, phone, payment_method (regex pre-export)
  • Sentry breadcrumb hook se window.Sentry presente

A.5 src/feature-flags.ts — flag reader

  • Default source: import.meta.env.PUBLIC_FEATURE_FLAGS (JSON)
  • Dev override: URL ?ff=key1:on,key2:off (warning console se prod)
  • Per-workspace override: lookup workspaces.feature_flags jsonb (Supabase, RLS scoped)
  • Audit log: ogni flip → INSERT INTO feature_flag_audit (workspace_id, flag, old_value, new_value, flipped_by, flipped_at) (table TODO escalate Mother se non esiste · placeholder mock for now)
  • LaunchDarkly-compatible shape per future migrazione

A.6 src/state-migrator.ts — versioned state migration

  • migrate(state: unknown, fromVersion: number, toVersion: number, migrations: MigrationSpec[]): Result<TState, MigrationError>
  • Forward + backward (rollback)
  • Migration spec: { from: number, to: number, up: (s) => s, down: (s) => s, validate: ZodSchema }
  • Replay test in planet-testkit gratis

A.7 src/healthcheck.ts — aggregated health

  • Endpoint /api/health/planets (Next.js route handler factory · export createHealthRoute())
  • Output: { [kind]: { status: 'ok'|'degraded'|'down', latency_ms, last_error?: string, version: SemVer } }
  • Per-planet healthcheck delegato a module.healthcheck()
  • Cache TTL 5s · stampede protection (single-flight)

A.8 src/index.ts — public API

  • Re-export tipi da @multiverbe/contracts
  • Re-export registry, loader, eventBus, telemetry, featureFlags, stateMigrator, healthcheck
  • Re-export defineModule<TState>(spec): PlanetModule<TState> helper (DX win)
  • NO internal types leaked (internal/ cartella con TypeScript path mapping)

A.9 Tests

  • tests/unit/*.test.ts per ogni file src/
  • tests/integration/registry-loader-bus.test.ts end-to-end registry+loader+bus
  • tests/integration/state-migration.test.ts forward + back roundtrip
  • Stryker config tarata su packages/planet-core/src/ (escludi index.ts re-exports)

B. packages/planet-testkit/

Test harness condiviso. Ogni PlanetModule<T> importa e ottiene 18 contract test gratis.

B.1 src/contract.ts

  • runContractSuite<TState>(module: PlanetModule<TState>, opts: ContractOpts): void
  • Test inclusi:
    1. manifest valid (Zod schema validation)
    2. manifest.id matches ^planet-[a-z][a-z0-9-]*$
    3. manifest.routes non-empty · each path starts with /
    4. manifest.rlsTables actually exist in Supabase (mock or real branch)
    5. init(ctx) returns Result<void, _> (no throws)
    6. init idempotent: chiamato 2x stesso effetto
    7. mount(slot) returns React.ComponentType
    8. unmount() cleanup completo (no listener leaks · process.getActiveResourcesInfo() baseline)
    9. selectors pure functions (referential transparency test)
    10. actions produce nuovo state object (immutability test)
    11. events types match manifest.telemetry.events
    12. healthcheck() <200ms p99
    13. State migration round-trip da v1 a latest
    14. Manifest bundleBudgetKb rispettato (size-limit assertion)
    15. A11y: mount root passa axe-core 0 critical
    16. Boot perf: ≤180ms p95 su 10 run
    17. Cross-workspace isolation: 2 workspace mount stesso planet → state separato
    18. Feature flag respect: manifest.featureFlags reader rispettato

B.2 src/rls.ts — Supabase RLS harness

  • verifyWorkspaceIsolation(table: TableName, supabase: SupabaseClient): Promise<RLSReport>
  • Create 2 workspaces test · INSERT in ws-A · query da ws-B expect deny
  • Cleanup automatico

B.3 src/a11y.ts — axe-core wrapper

  • expectA11y(html: string | HTMLElement, ruleset: 'wcag-2.2-aa' | 'wcag-2.2-aaa'): Promise<void>
  • Throws con violations array dettagliato

B.4 src/perf.ts — perf budget assertions

  • expectBootTime(planet: PlanetModule<unknown>, budget_ms: number, samples: number = 10): Promise<void>
  • Usa performance.now() · p95 calcolato

C. packages/planet-web/ retrofit

Wrap codice esistente Site Builder in PlanetModule<WebState> conforme. Code attualmente in frontend/multiverbe-app/src/sites-runtime/, src/components/sections/, src/orchestrator.ts.

C.1 Mapping codice esistente → PMS

Code esistente PlanetModule slot
sites-runtime/components/SiteRenderer.tsx module.mount() ritorna questo
src/orchestrator.ts (AI section selection) module.actions.generateSite
src/lib/sites/state.ts WebState tipo · selectors derivati
src/api/sites/* route handlers manifest routes + healthcheck endpoint
Section registry implicito manifest.routes[].sections: keyof SectionRegistry (typed strict)

C.2 Side-effect fixes (3 gap dal Wow Audit)

W-002 · Keyframes morti (sites-runtime/components/sections/Booking/SlideOver, StickyMobile, Gallery/InstagramGrid, Slideshow referenziano animate-[fadeIn]/[sheetUp]/[slideLeft] ma keyframes non definiti):

  • Crea packages/planet-web/src/styles/animations.css con @keyframes fadeIn, sheetUp, slideLeft (+ qualsiasi altro referenziato)
  • Import in module bootstrap
  • Aggiungi vitest test che scansiona JSX per animate-[...] e verifica esistenza in animations.css (catch new regressioni)
  • ADR? Solo se rimuovi animation invece di implementarle.

W-010 · Sezioni render null (SectionRenderer in components/sections/index.tsx ritorna null silenziosamente per sezioni non implementate):

  • Crea SectionRegistry typed strict: Record<SectionKind, React.ComponentType<SectionProps>>
  • AI section selection (orchestrator.ts) vincolata a keyof SectionRegistry (compile-time guard)
  • Sezioni candidate del Wow Audit (menu-story-driven, hero-food-bento, hero-video-loop, gallery-scroll-parallax, famiglie social-proof/about-story/private-events): decisione FOUNDER se implementare o restringere menù. Default: restringi menù (più sicuro · 1 ADR 004-section-registry-restricted-menu.md con lista deferred a Fase 4 Wow Backlog).
  • Test: render unknown section → fallback explicit <UnknownSectionWarning /> (non null silenzioso) · log warning + Sentry breadcrumb

W-011 · Screenshot stub (capturePageScreenshot() in orchestrator.ts:~1167 ritorna placeholder hardcoded):

  • Implementa real screenshot via Playwright headless launcher (server-side, lato Render Python o Node serverless)
  • Feature flag web.realScreenshot=true (default off in dev · on in prod)
  • Stub mode resta come fallback se Playwright launcher down (telemetry alert)
  • Test: integration con Playwright mock · screenshot reale on local stack

C.3 Manifest planet-web

export const manifest: PlanetManifest = {
  id: 'planet-web' as PlanetId,
  kind: 'web',
  version: '1.0.0',
  displayName: { it: 'Pianeta Web', de: 'Web-Planet', fr: 'Planète Web', en: 'Web Planet', es: 'Planeta Web' },
  description: { it: 'Generatore siti AI per PMI', /* ... */ },
  icon: { kind: 'lucide', name: 'Globe' },
  routes: [
    { path: '/web', component: 'WebDashboard', sections: ['hero', 'menu', 'booking-cta', 'gallery', 'footer'], prefetch: true },
    { path: '/web/builder', component: 'SiteBuilder', sections: [], prefetch: false },
    { path: '/web/preview/:siteId', component: 'SitePreview', sections: [], prefetch: false },
  ],
  permissions: ['web:read', 'web:write', 'web:publish'],
  rlsTables: ['sites', 'site_pages', 'site_assets', 'site_deployments'],
  telemetry: {
    spans: ['web.generate_site', 'web.preview', 'web.publish'],
    events: { siteGenerated: SiteGeneratedSchema, sitePublished: SitePublishedSchema },
  },
  featureFlags: [
    { key: 'web.realScreenshot', default: false, description: 'Use real Playwright screenshot vs stub' },
    { key: 'web.siteBuilderV2', default: false, description: 'Kimi K2.6 builder pipeline' },
  ],
  dependencies: [], // no soft deps
  stateSchemaVersion: 1,
  migrations: [],
  bundleBudgetKb: 80,
  a11yProfile: 'wcag-2.2-aa',
};

D. Scaffold script scripts/scaffold-planet.ts

CLI in root repo:

pnpm run scaffold:planet <kind>
# Esempio: pnpm run scaffold:planet crm

Output (in <60s):

  • packages/planet-{kind}/ skeleton:
    • package.json (name @multiverbe/planet-{kind}, peerDeps @multiverbe/contracts, @multiverbe/planet-core)
    • src/index.ts (export module placeholder)
    • src/manifest.ts (stub conforme · routes empty array · tutti i field richiesti)
    • src/state.ts (export interface {Kind}State {})
    • tests/contract.test.ts (importa planet-testkit runContractSuite(module))
    • tsconfig.json extends root
    • vitest.config.ts
    • README.md (template scope + boundaries + escalation contacts)
  • docs/adr/00X-planet-{kind}-implementation.md (template ADR)
  • D:\Desktop\Multiverbe-Business\source\_meta\status.json action_item planet-{kind}-implementation-{date} (status: todo, day: +3gg from today)
  • Smoke pnpm --filter @multiverbe/planet-{kind} test exit 0 immediato

Implementazione: usa prompts package per CLI prompts (kind, displayName, description), degit o template literal generation. Test del script via pnpm run scaffold:planet crm in CI smoke (poi delete via git clean -df packages/planet-crm).


Acceptance criteria (PR-blocking)

[ ] pnpm typecheck                          → exit 0 (zero any, zero @ts-ignore non commentato)
[ ] pnpm lint                               → zero new warning
[ ] pnpm --filter @multiverbe/planet-core test --coverage     → ≥80% line, ≥70% branch
[ ] pnpm --filter @multiverbe/planet-core test:mutation       → ≥60%
[ ] pnpm --filter @multiverbe/planet-testkit test             → 100% pass
[ ] pnpm --filter @multiverbe/planet-web test --coverage      → ≥70% line
[ ] pnpm --filter @multiverbe/planet-web build                → size-limit pass (≤80KB gz)
[ ] pnpm --filter @multiverbe/planet-core build               → size-limit pass (≤25KB gz)
[ ] pnpm dlx madge --circular packages/planet-core packages/planet-testkit packages/planet-web → 0 circular
[ ] Playwright smoke signup → /web → create site → preview    → zero error
[ ] axe-core scan su /web mount                               → 0 critical/serious
[ ] W-002 fix: keyframes test scanner pass + visual smoke 4 sezioni precedentemente bug   → pass
[ ] W-010 fix: SectionRegistry typed + 0 null section renders + UnknownSectionWarning explicit fallback   → pass
[ ] W-011 fix: real screenshot path implementato + feature flag wired + test coverage      → pass
[ ] Scaffold script pnpm run scaffold:planet crm <60s exit 0 + planet-crm test green       → pass
[ ] ADR 001 finalizzato con esempi reali · ADR 003 finalizzato                              → done
[ ] PR body: scope + impatto MVP% + screenshot Vercel preview + self-critique 3+ punti     → present
[ ] Commitlint conventional commits enforced (verified via git log)                         → pass

Time budget Child-1 (6h)

Slot Task Durata
0:00–0:30 Read brief + audit codebase esistente Web + setup local dev server 30min
0:30–2:00 planet-core/ registry + loader + state-migrator + healthcheck + tests 90min
2:00–3:30 planet-core/ event-bus + telemetry + feature-flags + tests (escalate Opus 4.7 su event-bus se serve) 90min
3:30–4:30 planet-testkit/ contract suite 18 test + RLS harness + a11y wrapper + perf budget 60min
4:30–5:30 planet-web/ retrofit + manifest + W-002/010/011 fix + tests 60min
5:30–6:00 Scaffold script + smoke + ADR finalize + PR open + self-critique 30min

Buffer 0 minutes. Scoping aggressivo voluto. Se scivoli >30min su un blocco → ESCALATE Mother subito · non aspettare.


Decision matrix · autonomous mode

Situazione Azione
Decisione naming/branding pubblico DECIDI best (semantic + i18n-ready) · logga in PR body · ADR-draft · proceed
Schema DB migration necessaria PROCEDI con safety rails · test branch ephemeral + UP/DOWN + RLS + get_advisors diff + ADR · apply via MCP apply_migration · proceed
Event bus semantics dubbie Time-box 90min · poi fallback EventEmitter typed semplice (mitt o custom) + ADR-005 deferring · proceed
Security trade-off (CSP, RLS, secret) DECIDI strict default · ADR · proceed · review post-merge in <quality_report>
Section deferral decision (W-010) DEFAULT = restringi menù (safer) · ADR-004 finalizza con lista deferred · proceed
Scope creep (oltre time budget) Slip ≥30min → riduci scope (skeleton invece di full) + action_item BOS per resto + PROCEED
Cycle detection bug Fix forward · refactor breve · proceed
CI fail post-PR Fix forward in stesso PR · NON aprire nuovo PR · proceed
Vercel deploy fail vercel logs + fix forward · feature flag rollback solo se prod broken end-user

Self-improvement loop Child-1

A fine sub-sessione, prima di chiudere PR:

  1. Self-critique sezione PR body (≥3 cose che farei meglio next time)
  2. Pattern learned snippet per Mother (Mother integra in memoria globale)
  3. Wow candidate ≥1 idea per execution/wow-backlog.md
  4. Handover 1 riga: cosa lascio fragile/da-rivedere · cosa lascio rock-solid

START COMMAND Child-1 (autonomous)

  1. Leggi questo brief intero
  2. Audit codebase: cd D:\Desktop\Multiverbe-child1-pms-core · pnpm install · pnpm test -r baseline · git log --oneline -20
  3. ACK 3 righe a Mother (1 task_spec · 1 risk · 1 confidence%) · NO domande · NO wait
  4. START SUBITO time-boxed seguendo § Time budget Child-1
  5. PR open: gh pr create --base master --title "feat(planet-system): PMS core + Web retrofit · W-002/010/011 fix" --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 3+ punti, decisioni zona grigia loggate)