product-demo-protocol v1.0.1, Modalita A. Additivo (zero impatto endpoint esistenti). - DemoController: session-start, manifest, event, credentials, reset-dataset * demo_jwt scope=demo:read-only, org_id 996001 (range demo 996000-996999), TTL 30m * hmac_key_seed base64 44ch, rate-limit 5/min+50/h per IP, eventi idempotenti - Migration 040: tabelle demo_sessions + demo_events - Rotte 'demo' in index.php (chiavi camelCase: router camelCasa il segmento URL) - docs/DEMO_AVATAR_NIS2.md: piano Fase1/Fase2, decisioni, punti integrazione AgileHub, gap protocollo Smoke prod OK: session-start 201, manifest 200, credentials 200, event 204, reset 401(no key). TODO Fase 1: read-only guard in requireAuth, dataset demo 996001, frontend demo-mode, manifest+mappe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
6.3 KiB
Markdown
69 lines
6.3 KiB
Markdown
# Avatar di prodotto NIS2 — Demo + Formazione (standard product-demo-complete)
|
||
|
||
> Stato: **Fase 1 (DEMO) in costruzione**. Owner prodotto: NIS2. Owner persona/voce/collaudo: AgileHub.
|
||
> Persona già LIVE: `ARIA_SUPPORT_NIS2` (id=4). Voce ARIA riusata (niente consenso voce).
|
||
> Modalità **A** (prodotto live: l'avatar pilota la SPA reale).
|
||
|
||
## Decisioni prese (2026-06-12)
|
||
1. **Scopo**: ENTRAMBI, in 2 fasi → Fase 1 DEMO (vetrina prospect ~10 step) poi Fase 2 FORMAZIONE (riusa i mattoni + curriculum/quiz/attestato).
|
||
2. **Completamento formazione**: **esame con soglia** (quiz valutato da LLM-giudice, attestato solo se ≥ soglia, es. 80%). Coerente col gate G7.
|
||
3. **Dominio same-origin**: **dimostrazione.agile.software** (come ALLTAX/TRPG): landing serve la SPA NIS2 in iframe same-origin con proxy `/api/*` → backend NIS2.
|
||
|
||
## Range riservato demo (NIS2)
|
||
`organization_id` **996000–996999** (per standard §7.1). Org demo isolata: **996001** (ente fittizio). Separata dalle org reali e dalle 2 golden-demo (#151/#152).
|
||
|
||
## Deliverable lato prodotto — DEMO (punti 1–5 del brief)
|
||
- [x] **Migration** `docs/sql/040_demo_sessions.sql` — tabelle `demo_sessions` + `demo_events`.
|
||
- [x] **DemoController** (`application/controllers/DemoController.php`) — 5 endpoint (vedi sotto).
|
||
- [x] **Rotte** `demo` registrate in `public/index.php`.
|
||
- [ ] **Guardia read-only + demo-JWT in `requireAuth`** (BaseController) — *step isolato, delicato: tocca l'auth di ogni endpoint*. Design sotto §Read-only.
|
||
- [ ] **Dataset demo isolato** (org 996001 + dati finti) + reset.
|
||
- [ ] **Frontend**: `public/js/demo-mode.js`, `public/js/demo-selectors.js`, `public/css/demo.css`, hook `?demo=` nell'entry-point.
|
||
- [ ] **Manifest** `public/demo/nis2-tour-2026.json` (~10 step IT; EN dopo).
|
||
- [ ] **Mappe** `docs/mappa-logica/*.md` (1 per vista) + `SELETTORI_DEMO.md` + anchor `data-demo-action` + id stabili nel DOM.
|
||
|
||
## Deliverable IN PIÙ — FORMAZIONE (punti 6–7, dopo la demo)
|
||
- [ ] Mappa-logica **COMPLETA** (tutte le viste, non solo le 10 della demo).
|
||
- [ ] **Tassonomia corso** (moduli → lezioni → argomenti, prerequisiti, durata). *Formato JSON da definire (lo standard non lo fissa).*
|
||
- [ ] (opz.) banca esercizi/domande per modulo + sandbox scrivibile isolata.
|
||
|
||
## Endpoint (route shape scelta)
|
||
Per non combattere il router NIS2 (path-param numerici), il `session_id` (stringa `demo-<uuid>`) viaggia in **query/body**, non in path. Le URL esatte che userà il widget sono quelle che ritorno io in `session-start` (`manifest_url`, `credentials_url`), quindi è conforme.
|
||
|
||
| Endpoint | Metodo | Auth | Note |
|
||
|---|---|---|---|
|
||
| `/api/demo/session-start` | POST | pubblico (rate-limit 5/min, 50/h per IP) | ritorna session_id, demo_jwt (scope=demo:read-only, org_id 996001, TTL 30m), hmac_key_seed (base64 44ch), spa_iframe_url, manifest_url, credentials_url, expires_at, presentation_mode |
|
||
| `/api/demo/manifest?session_id=` | GET | Bearer demo_jwt o session_id | ritorna il manifest tour |
|
||
| `/api/demo/event` | POST | Bearer demo_jwt | telemetria step (idempotente) |
|
||
| `/api/demo/credentials?session_id=` | GET | session valida (same-origin/bearer) | ritorna demo_jwt+org+expires (per bootstrap SPA in iframe) |
|
||
| `/api/demo/reset-dataset` | POST | `X-Internal-Key` | reset dataset demo (cron 0 3 * * *) |
|
||
|
||
## Read-only enforcement (design — da implementare con cura)
|
||
Il demo_jwt è messo dalla SPA in `localStorage.jwt` e usato sulle API reali (dashboard, risks, ...). Quindi `BaseController::requireAuth()` deve **riconoscere** il demo_jwt:
|
||
- se `payload.scope === 'demo:read-only'`: **bypassa** il check `jti`/`active_sessions`, imposta org corrente = `payload.org_id` (forzato nel range 996000–996999), e **blocca ogni verbo di scrittura** (POST/PUT/PATCH/DELETE) sulle API app → `403 DEMO_READ_ONLY` (eccetto gli endpoint `/api/demo/*`);
|
||
- range guard: demo_jwt fuori range → `403 DEMO_JWT_CANNOT_ACCESS_PRODUCTION`; JWT di produzione su org in range demo → `403 PRODUCTION_JWT_CANNOT_ACCESS_DEMO`.
|
||
- `getCurrentOrgId()` in demo ignora `X-Organization-Id` e usa l'org del JWT.
|
||
|
||
## Punti di integrazione DA CONFERMARE con AgileHub (gap dello standard)
|
||
1. **Canonical HMAC del canale postMessage**: lo standard ha **2 versioni divergenti** di `signDemoMessage`/`verifyDemoMessage` (ordine canonical `{message_id,...}` vs `{message_id,session_id,...}`, channelKey CryptoKey vs raw). Il `demo-mode.js` di NIS2 deve combaciare **byte-per-byte** col widget AgileHub deployato → **confermare quale canonical usa il widget live** prima dello smoke.
|
||
2. **Nome storage seed**: `bootstrapDemoAuth` legge `sessionStorage['demo_hmac_seed']` ma il campo API è `hmac_key_seed` → allineare chi scrive il seed in sessionStorage (widget) e chi lo legge (SPA).
|
||
3. **Same-origin / proxy** su `dimostrazione.agile.software` → `/api/*` verso backend NIS2 (config gateway lato AgileHub) + CSP che permetta l'iframe.
|
||
4. **Catalog/binding tour**: lato AgileHub servono `provider_resources_catalog (demo_tour)` + `agent_resource_bindings (demo_tour)` o il bootstrap meet-page aborta "no matching tour".
|
||
5. **Allineamento 3-way** DOM ↔ `demo-selectors.js` ↔ `SELETTORI_DEMO.md`: ogni cambio selettore → avvisare AgileHub per re-ingest RAG (dedup per content_hash → erasure-prima).
|
||
|
||
## Collaudo (definizione di "fatto")
|
||
DEMO: G1 selezione ≥95% PASS · **G2 parlato ≥8/10 e 0 leak** · G3 vista ≤5% falsi positivi · G4 sicurezza/GDPR verde.
|
||
FORMAZIONE (+): G5 copertura (nessun buco) · G6 accuratezza didattica · G7 validità assessment.
|
||
> "Fatto" solo quando passa il collaudo (mostra la cosa giusta, dice cose vere) — non quando l'avatar parla.
|
||
|
||
## Tour proposto (~10 step, sulla V2 reale)
|
||
1 Dashboard (score+scadenze) · 2 Assessment (gap Art.21) · 3 Rischi (matrice+trattamenti) · 4 Incidenti Art.23 (24h/72h) · 5 Policy · 6 Supply chain · 7 Asset (relevance GV.OC-04) · 8 Report · 9 Normative/ACN · 10 ARIA (assistente AI).
|
||
|
||
## Riferimenti
|
||
- Standard protocollo: `agile-services/docs/STANDARD_PRODUCT_DEMO_PROTOCOL.md` (v1.0.1)
|
||
- Standard metodo+collaudo: `agile-services/docs/STANDARD_PRODUCT_DEMO_COMPLETE.md`
|
||
- Requisiti NIS2: `agile-services/docs/OUTGOING_TO_NIS2_2026_05_29_demo_avatar_requirements.md`
|
||
- How-to mappe: `agile-services/docs/PLAYBOOK_DEMO_AVATAR_MAPPE.md`
|
||
- Esempio dev: `agile-services/docs/SPEC_ALLTAX_DEMO_AVATAR_PER_DEV.md`
|
||
- Formazione: `agile-services/docs/DESIGN_AVATAR_FORMAZIONE.md`
|