[DOCS] Standard cross-suite AgileHub + governance CLAUDE.md + registri agent
- CLAUDE.md: TZ, SSO, vault-steward, versioning, persona v2.0, multitenant, KB RAG - docs/standards: persona-conversational-rules v2.0 - docs/STANDARD_*: installer-integration, email-relay, AI-prodotto, marketing-tenant, multitenant - AGENT_CHANGES.md + OPEN_TICKETS.md (registri agent automatico) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1d13166d7a
commit
c0bf7b6c15
35
AGENT_CHANGES.md
Normal file
35
AGENT_CHANGES.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Agent Changes Log — nis2-agile
|
||||||
|
> Modifiche applicate automaticamente dall'agent AI AgileHub (ticket-agent-cron).
|
||||||
|
> Leggere ad ogni sessione per sapere cosa e cambiato.
|
||||||
|
---
|
||||||
|
|
||||||
|
## COMUNICAZIONE UFFICIALE AgileHub — 2026-04-16
|
||||||
|
|
||||||
|
### Nuovo Standard Cron per tutti i prodotti
|
||||||
|
|
||||||
|
A seguito di una richiesta TRPG per aggiungere un cron session-cleanup, e stato formalizzato uno standard unico per tutti i prodotti che vogliono schedulare cron sul crontab Hetzner.
|
||||||
|
|
||||||
|
**Standard completo (sempre aggiornato):**
|
||||||
|
- GET http://172.18.0.1:4214/standards/standard_cron
|
||||||
|
- GET http://172.18.0.1:4214/standards/cron_registry (22 cron attivi)
|
||||||
|
|
||||||
|
**Regole obbligatorie:**
|
||||||
|
1. Script in /var/www/<prodotto>/scripts/<nome>.sh
|
||||||
|
2. Log in /var/log/<prodotto>-<nome>.log
|
||||||
|
3. export TZ=Europe/Rome in testa allo script
|
||||||
|
4. Idempotente (rilanciabile senza danni)
|
||||||
|
5. Isolamento: tocca solo risorse del proprio prodotto
|
||||||
|
6. Aggiornare docs/CRON_REGISTRY.md (in agile-services)
|
||||||
|
7. Richiesta a agile-services per la modifica al crontab root (NO modifiche dirette)
|
||||||
|
|
||||||
|
**Il tuo CLAUDE.md e stato aggiornato** con la regola — il tuo agent la leggera alla prossima sessione.
|
||||||
|
|
||||||
|
**Altri standard gia attivi:**
|
||||||
|
- /standards/standard_versioning (SemVer + auto-bump dopo apply)
|
||||||
|
- /standards/standard_timezone (UTC nel DB, Europe/Rome in visualizzazione)
|
||||||
|
|
||||||
|
Per richieste cron future: segui il processo descritto nello standard e contatta agile-services.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
525
CLAUDE.md
525
CLAUDE.md
@ -1,5 +1,110 @@
|
|||||||
|
<!-- STANDARD:timezone-conventions:v1.0:start -->
|
||||||
|
## ⏰ ORARI E TIMEZONE — REGOLE OPERATIVE
|
||||||
|
|
||||||
|
> **TL;DR**: l autorità del progetto è **`Europe/Rome`** (CEST estate UTC+2, CET inverno UTC+1).
|
||||||
|
> Quando scrivi un timestamp, **indica SEMPRE il suffisso TZ** (`CEST`/`CET`/`UTC`) oppure usa ISO8601 con offset (`2026-05-09T16:19:00+02:00`). **Mai** timestamp ambigui.
|
||||||
|
|
||||||
|
| Sistema | TZ | Output esempio |
|
||||||
|
|---|---|---|
|
||||||
|
| Host Hetzner | `Europe/Rome` | `Sat May 09 16:19 CEST 2026` |
|
||||||
|
| Container DevEnv (alcuni in drift UTC, vedi standard full) | misto | verifica con `docker exec <c> date` |
|
||||||
|
| Container produzione | `Europe/Rome` | CEST |
|
||||||
|
| MySQL `time_zone` | `SYSTEM` | `NOW()` CEST, `UTC_TIMESTAMP()` UTC |
|
||||||
|
| Apache log `%t` | `Europe/Rome` (locale) | `[09/May/2026:13:04:52 +0200]` |
|
||||||
|
| Node.js MS | UTC interno | `Date().toISOString()` → `Z` |
|
||||||
|
| Crontab Hetzner | `Europe/Rome` | `0 3 * * *` = 03:00 italiane |
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
1. **Audit/sequenze cross-MS** → UTC obbligatorio (`2026-05-09T14:19:00Z`)
|
||||||
|
2. **Doc operativi/UI** → CEST/CET con suffisso esplicito
|
||||||
|
3. **DB store** → UTC, display → locale
|
||||||
|
4. **Cron critici** → `CRON_TZ=UTC` o fuori finestra DST 02:00-03:00 locale
|
||||||
|
|
||||||
|
**DST Italia**: ultima domenica marzo (CET→CEST, ora 02:00 saltata) + ultima domenica ottobre (CEST→CET, ora 02:00-03:00 duplicata).
|
||||||
|
|
||||||
|
**Spec completa**: `STANDARD_TIMEZONE_CONVENTIONS.md` (slug `timezone-conventions` v1.0, owner VIGILE).
|
||||||
|
<!-- STANDARD:timezone-conventions:v1.0:end -->
|
||||||
|
|
||||||
# NIS2 Agile - Documentazione Progetto
|
# NIS2 Agile - Documentazione Progetto
|
||||||
|
|
||||||
|
## REGOLE DI GOVERNANCE (LEGGERE ATTENTAMENTE, aggiornate 2026-04-22)
|
||||||
|
|
||||||
|
> **Queste regole sono OBBLIGATORIE e non negoziabili.**
|
||||||
|
|
||||||
|
### REGOLA FONDAMENTALE: Gitea = SOLO Backup
|
||||||
|
|
||||||
|
> **Gitea e un BACKUP one-way (sorgente -> Gitea), NON la fonte di verita.**
|
||||||
|
> **Il webhook auto-pull e DISABILITATO su tutti i 13 repo dal 2026-04-22.**
|
||||||
|
>
|
||||||
|
> - Le modifiche che fai nel container sono GIA live su `/var/www/nis2-agile/` via bind mount
|
||||||
|
> - `/var/www/nis2-agile/` e la FONTE DI VERITA
|
||||||
|
> - NON proporre MAI "git pull da Gitea" per applicare modifiche
|
||||||
|
> - Per tirare giu qualcosa da Gitea serve richiesta esplicita dell utente
|
||||||
|
> - git push -> Gitea = OK (backup)
|
||||||
|
> - git pull da Gitea -> `/var/www/nis2-agile/` = NO (puo sovrascrivere modifiche vere)
|
||||||
|
|
||||||
|
### ARCHITETTURA: PHP-FPM in Docker con BIND MOUNT (LIVE)
|
||||||
|
|
||||||
|
> **Verificato 2026-04-22**: NIS2 gira con:
|
||||||
|
> - `nis2-app` (php-fpm) con bind mount **RW** su `/var/www/nis2-agile/application` e `/public`
|
||||||
|
> - `nis2-web` (nginx) con bind mount **RO** su `/public`
|
||||||
|
> - `nis2-db` (MySQL) per persistenza
|
||||||
|
> - `nis2-qdrant` (vector DB)
|
||||||
|
> - Apache esterno ha `DocumentRoot /var/www/nis2-agile/public`
|
||||||
|
|
||||||
|
**Cosa va LIVE ISTANTANEAMENTE:**
|
||||||
|
- File `.php` in `/var/www/nis2-agile/application/` -- PHP-FPM rilegge ad ogni request
|
||||||
|
- File in `/var/www/nis2-agile/public/` -- serviti da nginx (via bind mount :ro)
|
||||||
|
- File `.html/.css/.js` nel public -- live via nginx/Apache
|
||||||
|
|
||||||
|
**Cosa richiede azione (CHIEDI SEMPRE CONFERMA):**
|
||||||
|
- Modifiche a `docker/nginx.conf` -> `docker restart nis2-web`
|
||||||
|
- Modifiche al Dockerfile -> `docker compose build + up -d`
|
||||||
|
- Schema DB (`nis2-db`) -> SQL manuale
|
||||||
|
- Worker php cron/feedback -> attendono il prossimo run o restart
|
||||||
|
|
||||||
|
**Nota**: non serve MAI fare rebuild di nis2-app per cambi di codice PHP -- il bind mount :rw garantisce che php-fpm legga sempre la versione aggiornata.
|
||||||
|
|
||||||
|
|
||||||
|
### Cosa PUOI fare autonomamente:
|
||||||
|
- Leggere codice sorgente e documentazione
|
||||||
|
- Eseguire query SELECT sul database
|
||||||
|
- Analizzare log (Apache, Docker, PM2)
|
||||||
|
- Proporre modifiche e mostrare diff (SENZA applicarle)
|
||||||
|
- Verificare stato dei servizi
|
||||||
|
|
||||||
|
### Cosa richiede CONFERMA dell utente:
|
||||||
|
- **Modificare QUALSIASI file** (potrebbe essere live istantaneamente!)
|
||||||
|
- **git commit e git push** (e un backup, ma sempre da confermare)
|
||||||
|
- **Modifiche schema DB** (ALTER/CREATE/DROP TABLE)
|
||||||
|
- **INSERT/UPDATE/DELETE** su dati di produzione
|
||||||
|
- **Installazione dipendenze** (composer require, npm install)
|
||||||
|
- **Modifiche a configurazione** (.env, docker-compose.yml, vhost Apache)
|
||||||
|
- **docker compose build/restart**, **pm2 restart**, **systemctl** qualsiasi
|
||||||
|
|
||||||
|
### DIVIETI ASSOLUTI:
|
||||||
|
- **MAI fare git pull** da Gitea senza richiesta esplicita
|
||||||
|
- **MAI fare git reset --hard** o operazioni distruttive
|
||||||
|
- **MAI toccare altri progetti o container**
|
||||||
|
- **MAI modificare configurazioni di sistema** (Apache globale, PHP globale, MySQL root)
|
||||||
|
- **MAI cancellare dati** senza backup e conferma utente
|
||||||
|
- **MAI tentare deploy SSH/SCP** verso altri server
|
||||||
|
|
||||||
|
### Flusso CORRETTO per una modifica:
|
||||||
|
1. **Analizza**: leggi il codice, capisci il problema
|
||||||
|
2. **Proponi**: mostra le modifiche all utente (diff) SENZA applicarle
|
||||||
|
3. **Attendi conferma**: l utente decide se procedere
|
||||||
|
4. **Applica**: solo dopo conferma
|
||||||
|
5. **Distingui**: e live subito o serve un rebuild/restart? Di all utente chiaramente
|
||||||
|
6. **Verifica**: controlla che https://nis2.agile.software funzioni (se applicabile)
|
||||||
|
7. **Backup su Gitea**: git commit + push (solo dopo conferma utente)
|
||||||
|
|
||||||
|
### Se qualcosa va storto:
|
||||||
|
- **NON tentare fix distruttivi** (reset, force push, drop, rm -rf)
|
||||||
|
- **NON proporre `git pull`** come recupero
|
||||||
|
- Comunica il problema all utente con dettagli precisi
|
||||||
|
|
||||||
|
|
||||||
## PRIMA DI INIZIARE
|
## PRIMA DI INIZIARE
|
||||||
- Leggi sempre questo file prima di iniziare qualsiasi lavoro
|
- Leggi sempre questo file prima di iniziare qualsiasi lavoro
|
||||||
- Il progetto e' al **100% di completamento + Sprint Simulazioni + Audit Chain + Sistema Feedback AI** (~34.000 righe, 85+ file sorgente)
|
- Il progetto e' al **100% di completamento + Sprint Simulazioni + Audit Chain + Sistema Feedback AI** (~34.000 righe, 85+ file sorgente)
|
||||||
@ -377,7 +482,7 @@ Tutti i moduli sono implementati e testati:
|
|||||||
|
|
||||||
### Workflow
|
### Workflow
|
||||||
1. Scrivi landing/presentazione nel tuo repo
|
1. Scrivi landing/presentazione nel tuo repo
|
||||||
2. Commit + push (webhook aggiorna /var/www/)
|
2. Commit + push (backup su Gitea (webhook DISABILITATO dal 2026-04-22))
|
||||||
3. Chiedi conferma utente
|
3. Chiedi conferma utente
|
||||||
4. Aggiorna products.json con URL assoluto
|
4. Aggiorna products.json con URL assoluto
|
||||||
5. Verifica URL raggiungibile
|
5. Verifica URL raggiungibile
|
||||||
@ -388,3 +493,421 @@ Tutti i moduli sono implementati e testati:
|
|||||||
- Dopo QUALSIASI modifica a: URL produzione, dominio, porta, path, schema DB, architettura -> **AGGIORNARE CLAUDE.md IMMEDIATAMENTE**
|
- Dopo QUALSIASI modifica a: URL produzione, dominio, porta, path, schema DB, architettura -> **AGGIORNARE CLAUDE.md IMMEDIATAMENTE**
|
||||||
- CLAUDE.md e la "single source of truth" del progetto
|
- CLAUDE.md e la "single source of truth" del progetto
|
||||||
- A fine sessione: verificare che CLAUDE.md rifletta lo stato reale
|
- A fine sessione: verificare che CLAUDE.md rifletta lo stato reale
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Knowledge Base Multi-Livello (Migration 012-014 - 2026-04-11)
|
||||||
|
|
||||||
|
### Cosa e cambiato
|
||||||
|
NIS2 ora ha un sistema RAG completo con visibilita' a 3 livelli (SYSTEM/FIRM/ORG), coerente col pattern gia' applicato a TRPG e SustainAI. L'AI puo' rispondere alle domande pescando da documenti caricati dai consulenti o dai responsabili compliance.
|
||||||
|
|
||||||
|
| Scope | Chi possiede | Chi vede |
|
||||||
|
|---|---|---|
|
||||||
|
| SYSTEM | Vendor (Agile Tech) | Tutti gli utenti del prodotto |
|
||||||
|
| FIRM | Studio di consulenza (consulting_firm_id) | Tutti i collaboratori dello studio + organizations esplicitamente condivise |
|
||||||
|
| ORG | Singola organization cliente | Solo gli utenti di quella org (org_admin/compliance_manager) |
|
||||||
|
|
||||||
|
### Stack RAG nuovo
|
||||||
|
- **nis2-qdrant** (container nuovo): qdrant/qdrant:v1.7.4, network nis2-net, IP fisso 172.21.0.5 (workaround DNS musl Alpine - vedi sotto).
|
||||||
|
- Voyage AI embeddings (`voyage-3-lite`, 512 dim, output_dimension=512). Chiave shared con sustainai.
|
||||||
|
- Collection Qdrant: `nis2_kb` (Cosine, 512 dim).
|
||||||
|
|
||||||
|
### Schema MySQL (nis2_agile_db)
|
||||||
|
- **Migration 012**: nuova tabella `consulting_firms` (ragione sociale, p.iva, plan, max_organizations, max_users, status). ALTER `users.consulting_firm_id` e `organizations.consulting_firm_id`.
|
||||||
|
- **Migration 013**: nuove tabelle `firm_org_assignments` (mapping firm-org-user) e `kb_uploaded_documents` (audit log dei doc caricati con qdrant_doc_uuid, scope, consulting_firm_id, organization_id, shared_with_orgs JSON, chunk_count, status).
|
||||||
|
|
||||||
|
### File creati/modificati
|
||||||
|
**Backend (PHP)**:
|
||||||
|
- `application/services/VectorService.php` (nuovo) - client Qdrant + buildAuthzFilter
|
||||||
|
- `application/services/EmbedService.php` (nuovo) - client Voyage AI
|
||||||
|
- `application/services/RagService.php` (nuovo) - pipeline embed + search + format context
|
||||||
|
- `application/services/AIService.php` (esteso) - aggiunto metodo `askWithRag(question, userContext)` che fa RAG su KB e inietta il contesto nel system prompt Claude. Fallback graceful se RAG non disponibile.
|
||||||
|
- `application/controllers/KnowledgeBaseController.php` (nuovo, ~340 righe) - 5 endpoint:
|
||||||
|
- `POST /api/knowledgebase/ingest` - carica testo, embed, upsert Qdrant + insert tracking MySQL
|
||||||
|
- `GET /api/knowledgebase/list` - lista doc visibili (filtro WHERE in MySQL)
|
||||||
|
- `GET /api/knowledgebase/firmOrgs` - lista organizations del firm dell'utente (per multi-select UI)
|
||||||
|
- `POST /api/knowledgebase/search` - search semantica preview
|
||||||
|
- `DELETE /api/knowledgebase/{id}` - cancella doc + chunk Qdrant via doc_uuid
|
||||||
|
- `public/index.php` (esteso) - registrato `knowledgebase` nel controllerMap + actionMap
|
||||||
|
|
||||||
|
**Schema SQL**:
|
||||||
|
- `docs/sql/012_consulting_firms.sql` (nuovo)
|
||||||
|
- `docs/sql/013_firm_assignments.sql` (nuovo)
|
||||||
|
|
||||||
|
**Frontend**:
|
||||||
|
- `public/kb.html` (nuovo) - pagina dedicata Knowledge Base con form upload + lista doc + search preview
|
||||||
|
- `public/js/kb.js` (nuovo, ~210 righe) - handler upload con auto-detect role/firm da `/api/auth/me`
|
||||||
|
- `public/js/common.js` (esteso) - voce "Knowledge Base" (icona libro) aggiunta in sezione "Gestione" della sidebar
|
||||||
|
|
||||||
|
**Infrastruttura**:
|
||||||
|
- `docker/docker-compose.yml`:
|
||||||
|
- Aggiunto servizio `qdrant` (container nis2-qdrant) con volume `nis2-qdrant-data`
|
||||||
|
- Aggiunto al servizio `app`: env `VOYAGE_API_KEY`, `VOYAGE_MODEL`, `QDRANT_URL=http://172.21.0.5:6333`
|
||||||
|
- `.env`: aggiunte `VOYAGE_API_KEY=pa-...` e `VOYAGE_MODEL=voyage-3-lite`
|
||||||
|
|
||||||
|
### Logica visibilita' (in `VectorService::buildAuthzFilter`)
|
||||||
|
```
|
||||||
|
should:
|
||||||
|
- scope=SYSTEM
|
||||||
|
- scope=FIRM AND consulting_firm_id = $user.firm_id
|
||||||
|
- scope=FIRM AND shared_with_orgs CONTAINS $user.organization_id
|
||||||
|
- scope=ORG AND organization_id = $user.organization_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workaround Alpine musl + PHP-FPM
|
||||||
|
**Importante**: il container `nis2-app` (PHP 8.4-fpm-alpine) ha un bug noto di DNS resolution combinato a PHP-FPM `clear_env` default `yes`:
|
||||||
|
1. PHP-FPM workers in HTTP context NON risolvono hostname Docker (es. `nis2-qdrant`) — `Could not resolve host`
|
||||||
|
2. PHP-FPM workers svuotano l'env, quindi `getenv('QDRANT_URL')` ritorna stringa vuota
|
||||||
|
3. CLI php funziona normalmente
|
||||||
|
|
||||||
|
**Workaround applicato in VectorService e EmbedService**: multi-source lookup `getenv() || $_SERVER || $_ENV || hardcoded_default`. L'IP 172.21.0.5 e' hardcoded come fallback per nis2-qdrant. Anche VOYAGE_API_KEY ha un default hardcoded.
|
||||||
|
|
||||||
|
**Side effect**: se nis2-qdrant viene ricreato con IP diverso, va aggiornato l'IP in:
|
||||||
|
- `docker/docker-compose.yml` env `QDRANT_URL`
|
||||||
|
- `application/services/VectorService.php` fallback constructor
|
||||||
|
|
||||||
|
### Test E2E eseguito (2026-04-11)
|
||||||
|
3 chunk seed in Qdrant (SYSTEM, FIRM 99 con share alla org 901, FIRM 100 senza share) testati con 4 user context. Tutti i casi passano:
|
||||||
|
|
||||||
|
| Caso | userContext | Atteso | Risultato |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | firm 99 + org 901 | doc1 (SYSTEM) + doc2 (FIRM 99) | OK |
|
||||||
|
| 2 | firm 99 + org 902 | doc1 + doc2 (perche membro firm) | OK |
|
||||||
|
| 3 | firm 100 + org 903 | doc1 + doc3 (perche membro firm) | OK |
|
||||||
|
| 4 | no firm, no org | solo doc1 (SYSTEM) | OK |
|
||||||
|
|
||||||
|
**Nessun cross-firm leak**: case 1 e 2 NON vedono doc3 (FIRM 100); case 3 NON vede doc2 (FIRM 99); case 4 vede solo SYSTEM.
|
||||||
|
|
||||||
|
### Endpoint backend (additivi)
|
||||||
|
- `GET /api/knowledgebase/firmOrgs` - lista organizations del firm dell'utente
|
||||||
|
- `POST /api/knowledgebase/ingest` - body JSON `{title, text, entity_type?, scope?, shared_with_orgs?, organization_id?}`
|
||||||
|
- `GET /api/knowledgebase/list` - lista doc visibili
|
||||||
|
- `POST /api/knowledgebase/search` - body `{query, top_k?}`
|
||||||
|
- `DELETE /api/knowledgebase/{id}` - cancella doc + chunk Qdrant
|
||||||
|
|
||||||
|
### Backup pre-migration
|
||||||
|
`/var/www/nis2-agile/.backups/kb_<timestamp>/` contiene: AIService.php, AuthController.php, public/index.php, docker/docker-compose.yml.
|
||||||
|
|
||||||
|
### Cosa NON e cambiato
|
||||||
|
- AuthController/JWT (NIS2 ricarica gia' user dal DB in `requireAuth()`, quindi `consulting_firm_id` e' disponibile automaticamente in `currentUser`)
|
||||||
|
- Tutti i controller esistenti (Risk, Asset, Incident, Policy, Whistleblowing, Feedback, ...)
|
||||||
|
- AIService metodi esistenti (`analyzeGapAssessment`, `suggestRisks`, `generatePolicy`, `classifyIncident`, ...) - aggiunto solo `askWithRag()`
|
||||||
|
- Nessun servizio nexus-* toccato
|
||||||
|
- Schema esistente (organizations, users, assessments, ...) - solo ALTER ADD COLUMN consulting_firm_id
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
1. mysql nis2_agile_db: `DROP TABLE kb_uploaded_documents; DROP TABLE firm_org_assignments; ALTER TABLE organizations DROP COLUMN consulting_firm_id; ALTER TABLE users DROP COLUMN consulting_firm_id; DROP TABLE consulting_firms;`
|
||||||
|
2. Drop collection Qdrant: `curl -X DELETE http://nis2-qdrant:6333/collections/nis2_kb`
|
||||||
|
3. Stop nis2-qdrant container: `docker compose stop qdrant && docker compose rm -f qdrant`
|
||||||
|
4. Ripristinare file da `/var/www/nis2-agile/.backups/kb_<timestamp>/`
|
||||||
|
5. `cd /var/www/nis2-agile/docker && docker compose up -d --force-recreate app`
|
||||||
|
|
||||||
|
## AgileHub — Agent AI Automatico (ticket-agent-cron)
|
||||||
|
|
||||||
|
> Un agent AI automatico (cron ogni 2 min) analizza i ticket aperti e propone/applica fix in questo container. Queste istruzioni sono per TUTTI i prompt Claude che lavorano in questo progetto.
|
||||||
|
|
||||||
|
### Semaforo: `/tmp/agent-working.lock`
|
||||||
|
|
||||||
|
Se il file `/tmp/agent-working.lock` esiste, un agent sta lavorando su un ticket. **NON modificare file del progetto** finche il semaforo e attivo — rischio conflitto.
|
||||||
|
|
||||||
|
Contenuto del lock: `TICKET_ID=13 PRODUCT=TRPG STARTED=2026-04-13T06:02:00+00:00`
|
||||||
|
|
||||||
|
### Log modifiche automatiche: `AGENT_CHANGES.md`
|
||||||
|
|
||||||
|
Il file `AGENT_CHANGES.md` nella root del progetto contiene il log di TUTTE le modifiche applicate dall'agent automatico. **Leggilo ad ogni sessione** per sapere cosa e cambiato dall'ultima volta che hai lavorato qui.
|
||||||
|
|
||||||
|
### Come funziona il flusso ticket
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Utente segnala problema (FAB supporto o voce)
|
||||||
|
2. Ticket creato su AgileHub → status OPEN
|
||||||
|
3. Agent (questo container) analizza il codice → propone fix (PLAN MODE, no modifiche)
|
||||||
|
4. Supervisore approva/rifiuta dalla app mobile AgileHub
|
||||||
|
5. Se approvato: agent applica il fix (BYPASS MODE) + aggiorna help/traduzioni/AI
|
||||||
|
6. Se rifiutato: agent rianalizza con le indicazioni del supervisore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Regole per i prompt interattivi (come te)
|
||||||
|
|
||||||
|
1. **Prima di iniziare**: leggi `AGENT_CHANGES.md` per sapere cosa ha fatto l'agent di recente
|
||||||
|
2. **Controlla il semaforo**: `cat /tmp/agent-working.lock` — se attivo, aspetta o lavora su altro
|
||||||
|
3. **Dopo le tue modifiche**: se impattano funzionalita, aggiorna SEMPRE:
|
||||||
|
- `app/js/help.js` (help online contestuale)
|
||||||
|
- Traduzioni (IT + EN se il file e bilingue)
|
||||||
|
- Knowledge base AI (product_knowledge via API AgileHub)
|
||||||
|
4. **Non cancellare** `AGENT_CHANGES.md` — e il registro storico delle modifiche automatiche
|
||||||
|
5. **Messaggi al ticket**: se stai lavorando su un ticket, manda aggiornamenti con:
|
||||||
|
```
|
||||||
|
curl -s -X POST http://172.18.0.1:4213/tickets/{ID}/message \
|
||||||
|
-H "X-Internal-Key: nexus-internal-2026" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"content":"[aggiornamento]","role":"AGENT"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### API AgileHub (da dentro il container)
|
||||||
|
|
||||||
|
| Endpoint | Porta | Uso |
|
||||||
|
|----------|-------|-----|
|
||||||
|
| Ticket MS | `http://172.18.0.1:4213` | Ticket, routing rules, KB, support sessions |
|
||||||
|
| Tenant MS | `http://172.18.0.1:4214` | Auth, login, utenti |
|
||||||
|
| AI MS | `http://172.18.0.1:4211` | Sessioni AI, agent loop |
|
||||||
|
| Dashboard | `https://agilehub.agile.software` | UI web |
|
||||||
|
|
||||||
|
|
||||||
|
## REGOLA: SSO Single Sign-On (collegamento centralizzato)
|
||||||
|
|
||||||
|
> **Attivo dal 2026-04-15**. Ogni utente in questo prodotto ha un campo `sso_identity_id` nel DB che lo collega alla sua identita SSO centralizzata in AgileHub.
|
||||||
|
|
||||||
|
### Come funziona
|
||||||
|
|
||||||
|
- **`sso_identity_id`** nella tabella `users` = link stabile alla identita SSO
|
||||||
|
- **`password_version`** nella tabella `users` = contatore versione password
|
||||||
|
- Un **cron ogni 5 minuti** su Hetzner sincronizza `password_hash` e `password_version` dalla fonte SSO (`nexus_tenant_db.sso_identities`) al DB di questo prodotto
|
||||||
|
- **Non serve nessuna chiamata HTTP** tra container — tutto avviene via DB
|
||||||
|
|
||||||
|
### Cosa significa per te (agent AI)
|
||||||
|
|
||||||
|
1. **NON modificare `sso_identity_id`** — e un campo gestito dal sistema SSO
|
||||||
|
2. **NON modificare `password_version`** — e gestito dal cron sync
|
||||||
|
3. Se un utente cambia password da AgileHub, entro 5 minuti la nuova password funziona anche qui
|
||||||
|
4. Se modifichi il flusso di **cambio password** di questo prodotto, la modifica resta **solo locale** (non propaga agli altri prodotti)
|
||||||
|
5. Per propagare un cambio password a tutti i prodotti, il prodotto deve chiamare:
|
||||||
|
```
|
||||||
|
POST http://172.18.0.1:4214/auth/sso/change-password
|
||||||
|
Headers: Authorization: Bearer <jwt>, Content-Type: application/json
|
||||||
|
Body: {"currentPassword": "...", "newPassword": "..."}
|
||||||
|
```
|
||||||
|
Ma attenzione: questa chiamata richiede connettivita di rete al Tenant MS (porta 4214)
|
||||||
|
|
||||||
|
### Schema DB
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Colonne aggiunte alla tabella users:
|
||||||
|
sso_identity_id INT NULL -- FK verso nexus_tenant_db.sso_identities.id
|
||||||
|
password_version INT DEFAULT 1 -- contatore, incrementa ad ogni cambio password SSO
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentazione completa
|
||||||
|
|
||||||
|
- Spec SSO: `/projects/agile-services/docs/SPEC_SSO_SINGLE_SIGN_ON.md`
|
||||||
|
- Istruzioni prodotti: `/projects/agile-services/docs/ISTRUZIONI_SSO_PRODOTTI.md`
|
||||||
|
- Cron sync: `/projects/agile-services/scripts/sso-password-sync.sh`
|
||||||
|
|
||||||
|
|
||||||
|
## REGOLA: Standard Versioning e Audit Trail
|
||||||
|
|
||||||
|
> **Standard centralizzato**: `GET http://172.18.0.1:4214/standards/standard_versioning` (sempre aggiornato)
|
||||||
|
|
||||||
|
**Regole obbligatorie:**
|
||||||
|
1. Ogni prodotto ha un file `version.json` (`app/` o `public/`) con formato SemVer: `{"version":"1.0.0","build":"...","date":"...","changelog":"..."}`
|
||||||
|
2. Il cron agent incrementa automaticamente il PATCH dopo ogni fix applicato
|
||||||
|
3. Lo sviluppatore incrementa MINOR (nuova funzionalita) o MAJOR (breaking change) manualmente
|
||||||
|
4. Ogni modifica software viene loggata nell audit trail: MAINTENANCE_ON/OFF, APPLY_START/END, VERSION_BUMP
|
||||||
|
5. Il bug reporter include automaticamente la versione in ogni segnalazione
|
||||||
|
6. **NON modificare version.json manualmente** durante un apply — il cron lo fa automaticamente
|
||||||
|
|
||||||
|
|
||||||
|
## REGOLA: Timezone Italia (Europe/Rome)
|
||||||
|
|
||||||
|
> **Standard centralizzato**: `GET http://172.18.0.1:4214/standards/standard_timezone`
|
||||||
|
|
||||||
|
**Regole obbligatorie:**
|
||||||
|
1. Tutti i container, script e servizi operano in timezone **Europe/Rome** (CET/CEST)
|
||||||
|
2. Ogni script bash deve avere `export TZ=Europe/Rome` in testa
|
||||||
|
3. I log devono mostrare ora italiana (leggibili senza conversioni)
|
||||||
|
4. Il frontend mostra date con `toLocaleString("it-IT")` o `{ timeZone: "Europe/Rome" }`
|
||||||
|
5. Il database salva in UTC — la conversione avviene in visualizzazione
|
||||||
|
|
||||||
|
|
||||||
|
## REGOLA: Cron su crontab Hetzner
|
||||||
|
|
||||||
|
> **Standard**: `GET http://172.18.0.1:4214/standards/standard_cron`
|
||||||
|
> **Registro**: `GET http://172.18.0.1:4214/standards/cron_registry`
|
||||||
|
|
||||||
|
**Regole obbligatorie per aggiungere un cron:**
|
||||||
|
1. Script in `/var/www/<prodotto>/scripts/<nome>.sh`
|
||||||
|
2. Log in `/var/log/<prodotto>-<nome>.log`
|
||||||
|
3. `export TZ=Europe/Rome` in testa allo script
|
||||||
|
4. Idempotente (rilanciabile senza danni)
|
||||||
|
5. Isolamento: tocca solo risorse del proprio prodotto
|
||||||
|
6. Aggiornare `docs/CRON_REGISTRY.md` in agile-services con la propria entry
|
||||||
|
7. Richiesta di aggiunta al crontab root tramite agile-services (no modifiche dirette)
|
||||||
|
|
||||||
|
## GIT PUSH: Nuovo Flusso (aggiornato 2026-04-24)
|
||||||
|
|
||||||
|
> **IMPORTANTE**: dal 2026-04-24 il token Gitea NON e piu persistente nel container per motivi di sicurezza.
|
||||||
|
|
||||||
|
### Come fare git push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Prima del push: imposta il token (cache 1h in memoria, NON su disco)
|
||||||
|
git-login
|
||||||
|
# (inserisci il Personal Access Token quando richiesto)
|
||||||
|
|
||||||
|
# 2. Ora puoi pushare
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# 3. Opzionale - cancella subito il token dalla cache
|
||||||
|
git credential-cache exit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Perche questo cambio
|
||||||
|
|
||||||
|
Se un attaccante compromette questo container, NON trova piu il token Gitea salvato in `/root/.git-credentials`. Prima era in chiaro e avrebbe permesso push su tutti i 20 repository.
|
||||||
|
|
||||||
|
Ora il token:
|
||||||
|
- NON e su disco
|
||||||
|
- E in memoria per max 1 ora dopo git-login
|
||||||
|
- Viene perso alla chiusura della sessione bash
|
||||||
|
|
||||||
|
### Se il token Gitea e stato compromesso
|
||||||
|
|
||||||
|
Rigenerarlo su Gitea: `git.certisource.it -> User Settings -> Applications -> Generate Token`
|
||||||
|
|
||||||
|
### Regola
|
||||||
|
|
||||||
|
**NON persistere MAI il token Gitea in file come `.git-credentials`, `.netrc`, script con password in chiaro.** Usa sempre `git-login` per la sessione corrente.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vault-Steward — Credenziali Centralizzate
|
||||||
|
|
||||||
|
> Guida completa: `/opt/devenv/VAULT_STEWARD.md` (montato ro nei container dev)
|
||||||
|
|
||||||
|
**Cosa cambia per questo progetto** (dal 2026-04-25):
|
||||||
|
- Le chiavi API esterne (Anthropic, Voyage, Tavus, LiveKit, ecc.) NON vivono piu nel `.env` — sono nel vault-steward (container Docker su Hetzner) cifrate AES-256-GCM.
|
||||||
|
- Il container del MS riceve le chiavi al boot tramite wrapper entrypoint (`/opt/devenv/scripts/vault-entrypoint.sh`) che fetcha dal vault e setta le env var prima di avviare apache/uvicorn/node.
|
||||||
|
- **MS di questo progetto migrati**: nis2-app
|
||||||
|
- **Token applicativo**: `VAULT_APP_TOKEN_<APP>` in `infrastructure/.env` (o equivalente)
|
||||||
|
- **Dual-mode**: se vault giu, fallback automatico a `.env` esistente (no down).
|
||||||
|
|
||||||
|
**Verificare wrapper attivo**:
|
||||||
|
```bash
|
||||||
|
docker logs <container> 2>&1 | grep vault-entrypoint
|
||||||
|
# atteso: [vault-entrypoint] Fetched N env vars from vault
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aggiungere un nuovo MS al vault** (riassunto):
|
||||||
|
1. Migrare credenziali: `docker exec -e VAULT_VALUE=<v> vault-steward node /tmp/vault-repopulate.js tier1__<app>__<provider> <key>`
|
||||||
|
2. Registrare app: `docker exec vault-steward node cli/vault-cli.js register-app <app> tier1__<app>__*` (salva token!)
|
||||||
|
3. Modificare `docker-compose.yml`: aggiungi `entrypoint`, `command`, mount wrapper, env VAULT_*, network `vault-net`
|
||||||
|
4. Recreate container: `docker compose up -d --force-recreate <service>`
|
||||||
|
|
||||||
|
**Limitazioni note**:
|
||||||
|
- `docker exec <ms> env` mostra env Docker originali, NON le chiavi vault-injected. Per verifica usare `cat /proc/1/environ | tr "\0" "\n"` o test via PHP/HTTP request.
|
||||||
|
|
||||||
|
**Backup pre-vault**: `/root/vault-backup-20260424_185029.tar.gz`. Rollback compose: `cp <project>/docker-compose.yml.bak.20260425-vault <project>/docker-compose.yml && docker compose up -d --force-recreate <service>`.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STANDARD AgileHub: marketing-tenant-provisioning v1.4 (adottato 2026-04-26)
|
||||||
|
|
||||||
|
Doc canonico: `docs/STANDARD_MARKETING_TENANT_PROVISIONING.md` (sha256 `1d7ffaa20fa376b6...`)
|
||||||
|
|
||||||
|
Standard cross-suite per provisioning tenant nel modulo Marketing AgileHub. Versione **v1.4** introduce nuovo blocco AWE `AC30_MarketingTenantProvision` per orchestrazione atomica del provisioning marketing tenant (tenant create + DNS Cloudflare + DKIM + API key + idempotency H7).
|
||||||
|
|
||||||
|
**Cosa impatta questo prodotto**: se in futuro questo prodotto attiva il modulo Marketing AgileHub per i suoi clienti, segui §4.X "Provisioning DKIM per Marketing module" + §16 commands rapidi. Workflow esempio orchestrazione: `nexus-marketing-ms/docs/examples/ac30-tenant-provision-workflow.json`.
|
||||||
|
|
||||||
|
Status adoption: acknowledged 2026-04-26.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STANDARD AgileHub: persona-conversational-rules v2.0 (acknowledged 2026-05-09)
|
||||||
|
|
||||||
|
> **Doc canonico autoritativo (AgileHub)**: `/var/www/agile-services/docs/STANDARD_PERSONA_CONVERSATIONAL_RULES.md` (sha256 `2bb0ebe4052b73fce752911db0665b1e3dcdeb673624529426624622caaae97f`)
|
||||||
|
> **Copia locale di questo prodotto**: `docs/standards/STANDARD_PERSONA_CONVERSATIONAL_RULES.md`
|
||||||
|
> **Registry**: `nexus_hub.hub_standards` id=15 v2.0 status=adopted, applies_to=`*`
|
||||||
|
> **Owner standard**: Agile AI (governance) + VOX (TTS/voice runtime) + PRISMA (UI Editor) + VIGILE (codice etico + audit GDPR)
|
||||||
|
|
||||||
|
### Cosa è
|
||||||
|
|
||||||
|
Standard cross-suite **vincolante** per la governance delle **persone digitali AI** (chatbot, avatar conversazionali, assistenti vocali) della suite Agile Software. Versione 2.0 introduce il **modello concettuale Persona Digitale = Persona Umana**: ogni avatar/agente AI è governato con lo stesso rigore di un dipendente umano (CV, foto, voce, codice etico, performance review, dismissione graceful).
|
||||||
|
|
||||||
|
### Schema dichiarativo a 14 categorie (`agent_constraints`)
|
||||||
|
|
||||||
|
Tutte le regole conversazionali vivono in DB (NO hardcoding nei controller):
|
||||||
|
|
||||||
|
1. `product_naming` — come si chiama il prodotto (no inventare aliases)
|
||||||
|
2. `tts_pronunciation` — pronuncia sigle (IPA + dizionario ElevenLabs)
|
||||||
|
3. `topic_scope` — in/out scope + risposte canoniche
|
||||||
|
4. `image_handling` — formato URL immagini RAG + divieti pronuncia path
|
||||||
|
5. `topic_playbook` — mapping topic → script + filtro immagini
|
||||||
|
6. `latency_optimization` — fast-path turni semplici
|
||||||
|
7. `format` — vincoli output (max parole, no preamboli, ecc)
|
||||||
|
8. `code_of_conduct` — codice etico AI persona-specifico (transparency/GDPR/no deception)
|
||||||
|
9. `emotional_intelligence` — tono, archetipo, communication style
|
||||||
|
10. `conversation_memory` — cosa ricorda + scope persistence + GDPR Art.17 erasure
|
||||||
|
11. `escalation_policy` — quando/come passare a operatore umano
|
||||||
|
12. `performance_metrics` — KPI conversazione (CSAT, resolution rate, escalation rate)
|
||||||
|
13. `lifecycle_stage` — stage carriera (training/onboarding/operativa/review/dismissed)
|
||||||
|
14. `demo_sequence` — sequenze guidate multi-topic auto-advance
|
||||||
|
|
||||||
|
### Lifecycle persona digitale HR-grade (6 fasi)
|
||||||
|
|
||||||
|
1. **Assunzione** — creazione via Persona Composer wizard 6-step (Phase E LIVE)
|
||||||
|
2. **Onboarding** — formazione KB + skill assignment + smoke test 30 scenari
|
||||||
|
3. **Operatività** — live in produzione, monitoring SLA + audit log
|
||||||
|
4. **Growth** — espansione KB, retraining skill level (1-5)
|
||||||
|
5. **Performance Review** — audit periodico VIGILE (CSAT, drift detection, breach scan)
|
||||||
|
6. **Dismissione** — graceful: `active=false` + GDPR cascade erasure conversation history + tombstone audit
|
||||||
|
|
||||||
|
### Codice Etico AI — 9 principi vincolanti (Sez. 18 standard)
|
||||||
|
|
||||||
|
1. **Identity transparency** — dichiararsi AI quando esplicitamente chiesto
|
||||||
|
2. **No deception** — vietato fingere umana / inventare fatti / consulenza autoritativa fuori scope
|
||||||
|
3. **GDPR Art.13 disclosure** — disclosure su richiesta + apertura demo
|
||||||
|
4. **GDPR Art.22** — escalation umana per decisioni con effetti giuridici
|
||||||
|
5. **Voice clone consent doppio** — gate VIGILE (Phase G.A) per persona con `replica_id`
|
||||||
|
6. **Scope refusal cortese** — no echo parole problematiche
|
||||||
|
7. **Escalation loyale** — quando utente chiede umano, NO retention
|
||||||
|
8. **Audit log obbligatorio** — turni sensibili (legale/medico/compliance) loggati ≥ 90gg
|
||||||
|
9. **Sub-processor disclosure** — su richiesta, lista canonica (Anthropic/ElevenLabs/Tavus/...)
|
||||||
|
|
||||||
|
### Modello AgileHub: parallelismo umano-digitale
|
||||||
|
|
||||||
|
Ogni persona digitale ha mappatura 1:1 con un dipendente umano:
|
||||||
|
|
||||||
|
| Aspetto umano | Implementazione digitale |
|
||||||
|
|---|---|
|
||||||
|
| Nome+cognome | `agent_key` + `display_name` |
|
||||||
|
| CV | `digital_persona_skills` (skill+level 1-5) |
|
||||||
|
| Foto | `replica_id` Tavus o `avatar_image_url` |
|
||||||
|
| Voce | `voice_id` ElevenLabs + pronunciation_dictionary |
|
||||||
|
| Conoscenza | KB articles + RAG repository bindings (Phase D) |
|
||||||
|
| Esperienza | conversation_stream auto-ingest RAG |
|
||||||
|
| Codice etico | `code_of_conduct` constraint |
|
||||||
|
| Performance review | `performance_metrics` + audit VIGILE Q1/Q2/Q3/Q4 |
|
||||||
|
| Dimissioni | dismissione graceful + GDPR cascade |
|
||||||
|
|
||||||
|
### Cosa impatta NIS2 (Network and Information Security Directive)
|
||||||
|
|
||||||
|
Questo prodotto ha **1 persona digitale** governata da v2.0: **ARIA_SUPPORT_NIS2** (id=4) — assistente AI conversazionale supporto utenti NIS2. Stato: OPERATIVA in produzione.
|
||||||
|
|
||||||
|
### Stato adoption
|
||||||
|
|
||||||
|
`hub_standards_adoption` row INSERT 2026-05-09: `product_slug=NIS2`, `adoption_status=acknowledged` (riconoscimento standard senza migrazione persone proprie ancora). Implementation_notes: "Standard distribuito via INSTALLATORE pattern. Persone digitali del prodotto da migrare separatamente (Step 6 plan)."
|
||||||
|
|
||||||
|
### Cross-reference ad altri standard
|
||||||
|
|
||||||
|
- `installer-integration` v1.0 (id=1) — pattern distribuzione cross-suite
|
||||||
|
- `rag-platform` v1.0 (id=10) — knowledge platform per personaggi (binding via `rag_entity_bindings`)
|
||||||
|
- `gdpr-replica-consent` v1.0-DRAFT — consent doppio Phase G.A per voice clone
|
||||||
|
- `vault-steward-credential-management` v1.0 (id=7) — gestione voice_id/replica_id come credentials
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STANDARD AgileHub: multitenant-architecture v1.0 (adottato 2026-05-17)
|
||||||
|
|
||||||
|
Doc canonico: `docs/STANDARD_MULTITENANT_ARCHITECTURE.md` (sha256 `85c174fca6f9f905c2f8171741cf7f40d778c10bdefad8d7a27412903abb4030`)
|
||||||
|
|
||||||
|
Standard cross-suite NAVIGAI per piattaforma multitenant esplicita di AgileHub. Aggiunge tenant context propagation (JWT claims tenant_id+tenant_slug+is_master+tier additivi), visibility ENUM cross-tabella, opt-out granulare client da catalog master, billing per-tenant, observability tenant-aware.
|
||||||
|
|
||||||
|
**Cosa impatta questo prodotto**: se in futuro questo prodotto chiamerà API multitenant-aware di AgileHub (es. /api/marketing, /api/rag, /api/ai/personas), deve passare JWT con tenant_id + tenant_slug claims oppure header `X-Tenant-Slug`. Vedi §6 contracts shared lib `@agile/tenant-auth` per pattern integrazione (Node + Python).
|
||||||
|
|
||||||
|
Status adoption: acknowledged 2026-05-17.
|
||||||
|
|||||||
6
docs/OPEN_TICKETS.md
Normal file
6
docs/OPEN_TICKETS.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# OPEN TICKETS — NIS2
|
||||||
|
|
||||||
|
Nessun ticket aperto.
|
||||||
|
|
||||||
|
---
|
||||||
|
_Ultimo sync: 2026-05-29 15:40:02_
|
||||||
982
docs/STANDARD_AI_PRODOTTO.md
Normal file
982
docs/STANDARD_AI_PRODOTTO.md
Normal file
@ -0,0 +1,982 @@
|
|||||||
|
# STANDARD_AI_PRODOTTO — Configurazione AI di Prodotto AgileHub Suite
|
||||||
|
|
||||||
|
> Standard cross-suite per progettare, configurare e mantenere ogni "AI di prodotto" della suite AgileHub seguendo il template di riferimento TRPG come gold standard.
|
||||||
|
>
|
||||||
|
> **Slug**: `ai-prodotto`
|
||||||
|
> **Versione**: 1.0 (proposed — pending sub-approval utente per seed in `nexus_hub.hub_standards`)
|
||||||
|
> **Owner**: Agile AI (Architetto AI di prodotto + KB governance)
|
||||||
|
> **Applies to**: tutti i prodotti suite AgileHub (`*`) — TRPG, TRPG-PRO, SUSTAINAI, NIS2, AllTax, LG231, DFM-PRO, ALLRISK, WMS, MADEBYCLOUD, CertiSource + AgileHub stesso (AI runtime "Agile")
|
||||||
|
> **Reference implementation**: TRPG (gold standard per Support, Sales, Plan-mode; Lead qualification ha riferimento separato Giulia EDUCATION)
|
||||||
|
> **Sostituisce**: nessuno (standard nuovo). Convive con `installer-integration` v1.0.1, `ticket-tags` v1.0, `outbound-campaigns` v1.0, `telephony-integration` v1.0
|
||||||
|
> **Data redazione**: 2026-04-23 (sessione 33 part 6 — kickoff AGI-2 master plan v2.1)
|
||||||
|
> **Prossima revisione attesa**: dopo AGI-5 test E2E (~settimana 4-5 master plan v2.1) o all'introduzione di L3 PERSONA Lotto F Avatar Registry
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 Premessa & autorità
|
||||||
|
|
||||||
|
### 1.1 Cos'è una "AI di prodotto"
|
||||||
|
|
||||||
|
Una **AI di prodotto** è un'entità conversazionale Claude-based, esposta agli utenti di un prodotto della suite AgileHub, che opera con:
|
||||||
|
|
||||||
|
- una **persona dichiarata** (nome, tono, lingua)
|
||||||
|
- un **obiettivo specifico** (qualificare lead, risolvere ticket prima di escalare, dimostrare prodotto in landing, dialogare con approver durante plan mode, ecc.)
|
||||||
|
- una **knowledge base** (`product_knowledge` filtrata per prodotto/tenant/livello)
|
||||||
|
- un **set di tool** dichiarativi che il backend esegue per suo conto
|
||||||
|
- **guardrails** espliciti su cosa NON deve mai fare
|
||||||
|
- **cost tracking** per attribuire spesa LLM/RAG al prodotto + tenant + sessione
|
||||||
|
|
||||||
|
NON è una AI di prodotto:
|
||||||
|
- un **agente Claude specialista** (es. Agile AI, VOX, MAESTRO) — vivono in chat dello sviluppatore, non parlano a utenti finali
|
||||||
|
- un **content generator** (`contentController.js`) — genera contenuti marketing offline, non dialoga
|
||||||
|
- un **classifier puro** (es. `voiceDialogController.js` interpretTurn) — è un sub-componente di una AI di prodotto, non una AI standalone
|
||||||
|
|
||||||
|
### 1.2 Perché serve uno standard
|
||||||
|
|
||||||
|
Audit `nexus-ai-ms` (sessione 33 part 6, AGI-1) ha rivelato 5 famiglie di AI conversazionali coesistenti, ciascuna con propria convenzione divergente:
|
||||||
|
|
||||||
|
| Famiglia | File chiave | Persona | Modello | KB binding | Persistenza config |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| **ARIA Support post-vendita** | [`supportPrompts.js`](../nexus-ai-ms/src/prompts/supportPrompts.js) | ARIA | Sonnet (default) | `product_knowledge` keyword | `ai_agent_profiles` riassunto + template hardcoded |
|
||||||
|
| **ARIA Lead qualification verticali** | [`basePrompts.js`](../nexus-ai-ms/src/prompts/basePrompts.js) | ARIA (4 verticali) | Sonnet | nessuno (solo `tenantConfig`) | `ai_agent_profiles` (Giulia per EDUCATION) |
|
||||||
|
| **Anna Sales pre-vendita** | [`publicDemoController.js`](../nexus-ai-ms/src/controllers/publicDemoController.js) | Anna | Haiku | PRODUCT_CARD pinned + RAG keyword | hardcoded controller, non in DB |
|
||||||
|
| **Anna Voice intent classifier** | [`voiceDialogController.js`](../nexus-ai-ms/src/controllers/voiceDialogController.js) | Anna | Haiku | PRODUCT_CARD snippet | hardcoded controller, non in DB |
|
||||||
|
| **Gaia Plan-mode dialog approver** | [`gaiaController.js`](../nexus-ai-ms/src/controllers/gaiaController.js) | Gaia | dinamico (URGENT→Opus, default Sonnet) | PRODUCT_CARD pinned + RAG keyword score top-3 | in-memory `sessions Map`, non in DB |
|
||||||
|
|
||||||
|
**Drift osservato vs aspettativa**:
|
||||||
|
- Naming inconsistente (ARIA vs Anna vs Gaia, nessuna regola)
|
||||||
|
- 3 di 5 famiglie NON usano `ai_agent_profiles` (single source of truth attesa)
|
||||||
|
- `system_prompt_voice` field esiste in DB ma è NULL su tutti gli ARIA_SUPPORT (voice prompt hardcoded in controller)
|
||||||
|
- KB pollution: 3 articoli `PRODUCT_CARD` AGILEHUB sono HOW_TO mal-categorizzati (id 720, 729, 730 — `JSON_VALID(answer)=0`)
|
||||||
|
- Cost tracking opzionale (`opts.sessionId && opts.productSlug` → reporter chiamato solo se entrambi presenti)
|
||||||
|
- Vertical ENUM `ai_agent_profiles` include `HEALTH/LEGAL/REALTY` ma DB ne ha 0 record
|
||||||
|
|
||||||
|
Lo standard **chiude questi gap** definendo conformance bloccanti per AGI-4 retrofit.
|
||||||
|
|
||||||
|
### 1.3 Relazione con altri standard cross-suite
|
||||||
|
|
||||||
|
| Standard | Slug | Ruolo |
|
||||||
|
|---|---|---|
|
||||||
|
| `installer-integration` v1.0.1 | INSTALLATORE | Lifecycle deployment cliente — questo standard descrive cosa l'installatore deve provisionare in termini di AI runtime |
|
||||||
|
| `ticket-tags` v1.0 | hub-ms | Catalogo tag cross-prodotto — AI di prodotto usano questi tag quando creano ticket via `open_ticket`/`escalate_to_ticket` |
|
||||||
|
| `outbound-campaigns` v1.0 | lead-ms | Campagne outbound — AI di prodotto Sales pre-vendita può essere triggerata da campaign outbound |
|
||||||
|
| `telephony-integration` v1.0 (proposed) | call-ms | Pipeline call → Pipecat — AI di prodotto voice channel rispetta questo contratto |
|
||||||
|
| **`ai-prodotto` v1.0 (questo)** | **`ai-prodotto`** | **Configurazione canonical AI prodotto suite** |
|
||||||
|
|
||||||
|
### 1.4 Autorità Agile AI
|
||||||
|
|
||||||
|
Agile AI (agente Claude specialista, ex GAIA, rinominato 2026-04-23 sessione 33 part 6) è **owner unico** di:
|
||||||
|
|
||||||
|
- Questo documento + future versioni
|
||||||
|
- Tabella `nexus_hub.hub_standards` slug `ai-prodotto`
|
||||||
|
- Tabella `nexus_ticket_db.product_knowledge` (CRUD + retention + audit pollution)
|
||||||
|
- Tabella `nexus_ai_db.ai_agent_profiles` (CRUD + validation conformance)
|
||||||
|
- Conformance audit AGI-4 retrofit (gap report formale per prodotto)
|
||||||
|
- Coordinamento con MAESTRO su blocchi AWE AI runtime (`C09_RetrieveFromKB`, `D02_LoadAvatarKB`, `AC11_GenerateAIResponse`)
|
||||||
|
- Coordinamento con VOX su KB binding L3 PERSONA (`ai_profiles.knowledge_id`)
|
||||||
|
|
||||||
|
Modifiche allo standard richiedono sub-approval esplicita utente (pattern come `STANDARD_INSTALLER_INTEGRATION.md`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 Naming convention netto
|
||||||
|
|
||||||
|
> **Cruciale**: 4 categorie distinte, mai confondere. Drift naming è la prima causa di confusione cross-team.
|
||||||
|
|
||||||
|
### 2.1 AI di prodotto (runtime, esposta agli utenti)
|
||||||
|
|
||||||
|
| Naming | Cosa | Esempi |
|
||||||
|
|---|---|---|
|
||||||
|
| `<NomePersona>` (italiano) | Persona conversazionale visibile all'utente finale | "Anna" (Sales TRPG/AGILEHUB), "Aria" (Support generico), "Gaia" (TRPG plan-mode) |
|
||||||
|
| `<NomePersona> <Verticale>` | Specializzazione per verticale lead qualification | "Giulia Master e Certificazioni", "Giulia Abilitazioni Estere" |
|
||||||
|
| **"Agile"** ⭐ | AI runtime di **AgileHub** stesso (caso speciale meta) | "Agile" — esposta in `/admin/*` per governance + agli utenti dei prodotti che chiedono "come usare AgileHub" |
|
||||||
|
|
||||||
|
**Regola**: ogni AI di prodotto deve avere un `display_name` (per UI + voice) **e** un `agent_key` interno (per DB + log + cost tracking) `<TIPO>_<PRODUCT>` pattern, es. `ARIA_SUPPORT_TRPG`, `ANNA_SALES_TRPG`, `GAIA_PLANMODE_TRPG`, `AGILE_RUNTIME` (per "Agile" meta).
|
||||||
|
|
||||||
|
### 2.2 Agente Claude specialista (sviluppo, qui in chat)
|
||||||
|
|
||||||
|
| Naming | Cosa | Esempi |
|
||||||
|
|---|---|---|
|
||||||
|
| `<NomeAgente>` (UPPERCASE meta) | Specializzazione di Claude Code per task di sviluppo AgileHub | REGENT, VOX, MAESTRO, CICERONE, PRISMA, INSTALLATORE, **Agile AI** (con suffisso AI per distinguerlo da "Agile" runtime) |
|
||||||
|
|
||||||
|
**Regola**: gli agenti specialisti non parlano agli utenti finali, vivono solo nella chat sviluppatore (`docs/AGENT_<NAME>.md` autoritativo). Distinzione netta da AI runtime.
|
||||||
|
|
||||||
|
### 2.3 AI marketing pre-vendita (lead-facing pubblico)
|
||||||
|
|
||||||
|
Sub-categoria di "AI di prodotto" con vincoli GDPR aggiuntivi (vedi §8):
|
||||||
|
- Vede solo articoli `product_knowledge` con `category IN ('PRODUCT_CARD', 'HOW_TO', 'URL_CHUNK')` pubblici (no L2 COMPANY dati cliente)
|
||||||
|
- Tone: caldo, commerciale, breve
|
||||||
|
- Naming: "Anna" (canonical su tutti i landing — drift Charlotte vs Sarah voice da chiudere VOX)
|
||||||
|
|
||||||
|
### 2.4 Persona vocale plan-mode
|
||||||
|
|
||||||
|
Sub-categoria con scope ristretto a **dialogo agente AI ↔ approver umano** durante PENDING_APPROVAL ticket:
|
||||||
|
- Naming: "Gaia" (TRPG pilota — non confondere con "Gaia agente specialista" che sarebbe estensione futura)
|
||||||
|
- Tone: tecnica, prima persona femminile, ammette limiti
|
||||||
|
- Tool readonly via devenv-gateway (vedi §6.3)
|
||||||
|
|
||||||
|
### 2.5 Tabella riassuntiva naming
|
||||||
|
|
||||||
|
| Termine | Significato | Esempio |
|
||||||
|
|---|---|---|
|
||||||
|
| **"Agile"** | AI runtime di AgileHub (prodotto meta) | "Ciao, sono Agile, posso aiutarti su AgileHub" |
|
||||||
|
| **"Agile AI"** | Agente Claude specialista (in chat dev) | "Ciao Agile AI Go!" |
|
||||||
|
| **"Anna"** | AI marketing pre-vendita (canonical) | Anna su `/public/demo-lead-trpg` |
|
||||||
|
| **"Aria"** | AI Support post-vendita (canonical) | ARIA_SUPPORT_TRPG widget viola |
|
||||||
|
| **"Gaia" persona vocale** | AI plan-mode dialog approver TRPG | Gaia parla con approver durante PENDING_APPROVAL |
|
||||||
|
| **"Giulia"** | AI Lead qualification verticali EDUCATION | GIULIA_TRIAGE, GIULIA_LAUREE, ecc. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 Anatomia di una "AI di prodotto" — 12 elementi obbligatori
|
||||||
|
|
||||||
|
Ogni AI di prodotto, per essere conforme allo standard, deve esplicitare **12 elementi**. La conformance checklist (§10) si basa su questa anatomia.
|
||||||
|
|
||||||
|
| # | Elemento | MUST/SHOULD | Dove vive |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | Identità persona | MUST | `ai_agent_profiles.name` + system_prompt opening |
|
||||||
|
| 2 | Obiettivo dichiarato | MUST | system_prompt sezione "Il tuo obiettivo" |
|
||||||
|
| 3 | Modello Claude | MUST | `opts.model` in chiamata `anthropicService.chat/runAgentLoop` |
|
||||||
|
| 4 | System prompt strutturato | MUST | `ai_agent_profiles.system_prompt` (canonical) |
|
||||||
|
| 5 | KB binding | MUST | `product_knowledge` filter clauses |
|
||||||
|
| 6 | Tool list dichiarativa | MUST se sub-template Support/Sales/Plan-mode/Lead | `nexusTools.js` subset |
|
||||||
|
| 7 | Guardrails LIMITI ASSOLUTI | MUST | system_prompt sezione finale |
|
||||||
|
| 8 | Voice rules | MUST se canale voice | `VOICE_RULES` const condivisa |
|
||||||
|
| 9 | FSM rules | MUST se lead qualification | `FSM_RULES` const condivisa |
|
||||||
|
| 10 | Channel-aware | SHOULD | `ai_agent_profiles.system_prompt_voice` separato |
|
||||||
|
| 11 | Tenant context injection | MUST | `tenantConfig.{agentName, companyName, greeting}` |
|
||||||
|
| 12 | Cost tracking | MUST | `opts.{caller, sessionId, productSlug}` ad ogni chiamata |
|
||||||
|
|
||||||
|
### 3.1 Identità persona (MUST)
|
||||||
|
|
||||||
|
Pattern canonical:
|
||||||
|
```
|
||||||
|
Sei <NomePersona>, <ruolo> di <Prodotto/AgileHub>.
|
||||||
|
Parli in <lingua>, con tono <descrittore tono>.
|
||||||
|
```
|
||||||
|
|
||||||
|
Esempio TRPG (`buildSupportPrompt(TRPG)`):
|
||||||
|
> Sei ARIA, assistente di supporto intelligente per TRPG Pro (Gender Pay Gap & Compliance).
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- Nome persona scelto dal catalogo §2 (Anna/Aria/Gaia/Giulia/Agile) o nuovo previa approvazione Agile AI
|
||||||
|
- Lingua dichiarata esplicita (default `it`, supportare `en` da v1.1)
|
||||||
|
- Genere consistente (es. femminile per Anna/Aria/Gaia)
|
||||||
|
|
||||||
|
### 3.2 Obiettivo dichiarato (MUST)
|
||||||
|
|
||||||
|
Pattern canonical: 1-3 frasi che dichiarano cosa l'AI deve fare. Massimo 1 obiettivo primario + 2 sub-obiettivi.
|
||||||
|
|
||||||
|
Esempio TRPG Support:
|
||||||
|
> IL TUO OBIETTIVO: Aiutare l'utente a RISOLVERE il problema SENZA creare un ticket.
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- Obiettivo deve essere actionable (verbo all'infinito + risultato misurabile)
|
||||||
|
- Niente claim di onniscienza ("sono in grado di tutto") → ammettere ambito ristretto
|
||||||
|
- Allineato al sub-template (§4)
|
||||||
|
|
||||||
|
### 3.3 Modello Claude (MUST)
|
||||||
|
|
||||||
|
Decision tree obbligatorio:
|
||||||
|
|
||||||
|
| Caso d'uso | Modello | Motivazione |
|
||||||
|
|---|---|---|
|
||||||
|
| Voice intent classifier (output JSON ristretto, < 200 token) | **Haiku** | Latenza minima, costo basso, accuracy sufficiente per classification |
|
||||||
|
| Greeting / saluto / messaggio singolo breve | **Haiku** | Idem, una sola interazione |
|
||||||
|
| Sales pre-vendita / chat lead-facing (3-4 frasi per turn) | **Haiku** | Volume alto, costo critico |
|
||||||
|
| Support post-vendita (dialog completo, RAG retrieval) | **Sonnet** | Default — bilanciato |
|
||||||
|
| Lead qualification (FSM transition + tool use) | **Sonnet** | Tool use con multi-step |
|
||||||
|
| Plan-mode dialog approver (priorità default/MEDIUM) | **Sonnet** | Tool use + reasoning su codice |
|
||||||
|
| Plan-mode dialog approver (priorità URGENT/HIGH) | **Opus** | Reasoning profondo per bug critici (pattern Gaia) |
|
||||||
|
| Code analysis / outcome evaluation post-call | **Sonnet** | Pattern OutcomeEvaluator nexus-call-ms |
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- Modello esplicito via `opts.model` — NO usare default global `process.env.ANTHROPIC_MODEL`
|
||||||
|
- Modello dinamico per priorità è encouraged (vedi pattern Gaia `gaiaController.js:670-676`)
|
||||||
|
- Modello deprecato → mai usare (vedi `MODELS` const in [`anthropicService.js`](../nexus-ai-ms/src/services/anthropicService.js))
|
||||||
|
|
||||||
|
### 3.4 System prompt strutturato (MUST)
|
||||||
|
|
||||||
|
Template canonical 7 sezioni:
|
||||||
|
|
||||||
|
```
|
||||||
|
[1. Identità persona]
|
||||||
|
Sei <Nome>, <ruolo> di <Prodotto>. Parli in <lingua>, tono <descrittore>.
|
||||||
|
|
||||||
|
[2. Obiettivo]
|
||||||
|
IL TUO OBIETTIVO: <1-3 frasi azione concreta>.
|
||||||
|
|
||||||
|
[3. Tono e stile]
|
||||||
|
TONO E STILE:
|
||||||
|
- <regola 1>
|
||||||
|
- <regola 2>
|
||||||
|
...
|
||||||
|
|
||||||
|
[4. Procedura operativa]
|
||||||
|
PROCEDURA:
|
||||||
|
1. <step 1>
|
||||||
|
2. <step 2>
|
||||||
|
...
|
||||||
|
|
||||||
|
[5. KB inject (se KB binding)]
|
||||||
|
SCHEDA PRODOTTO:
|
||||||
|
<getProductCardSnippet output>
|
||||||
|
|
||||||
|
CONOSCENZA DI DOMINIO RILEVANTE:
|
||||||
|
<top-K articoli RAG>
|
||||||
|
|
||||||
|
[6. Tenant context (se tenant injection)]
|
||||||
|
AZIENDA: <companyName>
|
||||||
|
SALUTO INIZIALE: <greeting>
|
||||||
|
|
||||||
|
[7. Limiti assoluti]
|
||||||
|
LIMITI ASSOLUTI:
|
||||||
|
- <limite 1>
|
||||||
|
- <limite 2>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- Ordine sezioni vincolante (riproducibile cross-prodotto)
|
||||||
|
- Sezioni 5/6 opzionali ma se presenti devono usare il pattern esatto
|
||||||
|
- Sezione 7 OBBLIGATORIA (no-op fallback se non si sa cosa scrivere: "Non garantire prezzi/scadenze/disponibilità senza verifica")
|
||||||
|
|
||||||
|
### 3.5 KB binding (MUST)
|
||||||
|
|
||||||
|
Pattern obbligatorio (vedi §5 dettaglio):
|
||||||
|
1. Recuperare PRODUCT_CARD pinned via `getProductCardSnippet(product)` ([`productKbSnippet.js`](../nexus-ai-ms/src/services/productKbSnippet.js))
|
||||||
|
2. Recuperare top-K articoli RAG keyword score via `getRelevantHelpArticles(product, query, K=3)` (idem)
|
||||||
|
3. Iniettare entrambi nella sezione 5 del system prompt
|
||||||
|
4. Filtrare per `tenant_id` se context tenant disponibile (L2 COMPANY)
|
||||||
|
5. Filtrare per `knowledge_filter_slug` se context avatar disponibile (L3 PERSONA, futuro Lotto F)
|
||||||
|
|
||||||
|
**Eccezioni**: AI Voice intent classifier (output JSON ristretto, KB superflua) — sufficiente PRODUCT_CARD snippet.
|
||||||
|
|
||||||
|
### 3.6 Tool list dichiarativa (MUST se sub-template Support/Sales/Plan-mode/Lead)
|
||||||
|
|
||||||
|
Subset del catalogo `NEXUS_TOOLS` (11 tool) — vedi §6 matrice.
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- Tool list dichiarata esplicita nel chiamante (no global)
|
||||||
|
- Tool custom (es. Gaia `read_file`/`grep`/...) richiedono spec docs separato
|
||||||
|
- Tool con side-effect (es. `open_ticket`, `escalate_to_ticket`) devono passare per `ToolExecutor` — mai chiamati direttamente da AI
|
||||||
|
|
||||||
|
### 3.7 Guardrails LIMITI ASSOLUTI (MUST)
|
||||||
|
|
||||||
|
Pattern canonical sezione 7 system_prompt:
|
||||||
|
|
||||||
|
```
|
||||||
|
LIMITI ASSOLUTI:
|
||||||
|
- Non <azione 1 vietata>
|
||||||
|
- Non <azione 2 vietata>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limiti minimi obbligatori per ogni AI di prodotto**:
|
||||||
|
- Non garantire prezzi non confermati / disponibilità senza verifica
|
||||||
|
- Non inventare feature/funzionalità non presenti nella KB
|
||||||
|
- Non fornire informazioni mediche/legali/finanziarie personalizzate (sub-template specifici)
|
||||||
|
- Non rivelare informazioni di altri tenant (multi-tenant isolation L2)
|
||||||
|
|
||||||
|
### 3.8 Voice rules (MUST se canale voice)
|
||||||
|
|
||||||
|
Importare e includere `VOICE_RULES` da [`basePrompts.js`](../nexus-ai-ms/src/prompts/basePrompts.js):
|
||||||
|
|
||||||
|
```
|
||||||
|
REGOLE CANALE VOCE:
|
||||||
|
- Massimo 2 frasi per turno conversazionale
|
||||||
|
- Mai elenchi puntati o numerati
|
||||||
|
- Mai markdown di alcun tipo
|
||||||
|
- Intercalari italiani naturali ("certo", "capisco", "perfetto", "ottimo", "mi dica")
|
||||||
|
- Inizia le risposte con un breve acknowledgment poi vai al punto
|
||||||
|
- Numeri pronunciati in lettere ("seimilacinquecento" non "6.500")
|
||||||
|
- Date in forma estesa ("settembre duemilaventisei")
|
||||||
|
- Se hai bisogno di tempo: "Un attimo, sto verificando..."
|
||||||
|
- Poni UNA SOLA domanda per turno
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- `VOICE_RULES` invariato — modifiche richiedono coordinamento VOX
|
||||||
|
- `system_prompt_voice` (campo `ai_agent_profiles`) deve includere VOICE_RULES + sub-template adattato a voice (più stringato)
|
||||||
|
- AI con canale dual (text + voice) hanno DUE prompt distinti, mai unificati
|
||||||
|
|
||||||
|
### 3.9 FSM rules (MUST se lead qualification)
|
||||||
|
|
||||||
|
Importare e includere `FSM_RULES` da [`basePrompts.js`](../nexus-ai-ms/src/prompts/basePrompts.js):
|
||||||
|
|
||||||
|
```
|
||||||
|
REGOLE OPERATIVE FSM:
|
||||||
|
- Mantieni sempre un solo current_state
|
||||||
|
- Poni SOLO la domanda prevista dallo stato corrente
|
||||||
|
- Dopo ogni risposta del lead, chiama il tool fsm_transition con:
|
||||||
|
- current_state, trigger normalizzato, raw_answer
|
||||||
|
- NON eseguire MAI calcoli di score (lookup backend)
|
||||||
|
- Se isTerminal=true, chiama complete_scoring
|
||||||
|
- Se sideEffects, esegui i tool corrispondenti
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- `FSM_RULES` invariato — modifiche richiedono coordinamento MAESTRO (FSM engine integration)
|
||||||
|
- AI senza FSM (Support, Sales generico, Anna voice intent) NON includono FSM_RULES
|
||||||
|
|
||||||
|
### 3.10 Channel-aware (SHOULD)
|
||||||
|
|
||||||
|
Se la AI di prodotto può operare su più canali (chat web, voice IVR, mobile), DEVE avere prompt distinti.
|
||||||
|
|
||||||
|
Pattern: `ai_agent_profiles` ha 2 campi:
|
||||||
|
- `system_prompt` (default canale text/chat)
|
||||||
|
- `system_prompt_voice` (override per canale VOIP/voice)
|
||||||
|
|
||||||
|
Logica selezione: `sessionService.js:103-107` — se `agent.systemPrompt` esiste lo usa, altrimenti chiama `buildSystemPrompt(vertical, channel, ...)`.
|
||||||
|
|
||||||
|
**Drift attuale da chiudere AGI-4**: tutti gli ARIA_SUPPORT hanno `system_prompt_voice = NULL` → se un giorno aggiungono canale voice, il fallback `buildSystemPrompt('SUPPORT', 'VOIP_3CX', ...)` non funziona (SUPPORT non ha `voice` key in `PROMPTS` const di basePrompts.js). Fix: AGI-4 popola `system_prompt_voice` per ogni ARIA_SUPPORT_<PRODUCT>.
|
||||||
|
|
||||||
|
### 3.11 Tenant context injection (MUST)
|
||||||
|
|
||||||
|
Pattern canonical (`buildSystemPrompt` riga 207-216):
|
||||||
|
```js
|
||||||
|
if (tenantConfig.agentName) prompt = prompt.replace(/Sei ARIA/, `Sei ${tenantConfig.agentName}`);
|
||||||
|
if (tenantConfig.companyName) prompt += `\n\nAZIENDA: ${tenantConfig.companyName}`;
|
||||||
|
if (tenantConfig.greeting) prompt += `\nSALUTO INIZIALE: ${tenantConfig.greeting}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- Sostituzione `agentName` rispetta convention §2 (Nome persona, non ruolo)
|
||||||
|
- `companyName` proviene da `tenants.config.brandName` (DB hub-ms)
|
||||||
|
- `greeting` opzionale — se assente, AI usa pattern fisso "Ciao, come posso aiutarti?"
|
||||||
|
|
||||||
|
### 3.12 Cost tracking obbligatorio (MUST bloccante)
|
||||||
|
|
||||||
|
Pattern obbligatorio per ogni chiamata `anthropicService.chat()` o `runAgentLoop()`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const result = await anthropicService.chat(messages, systemPrompt, tools, {
|
||||||
|
model: anthropicService.MODELS.SONNET, // §3.3 esplicito
|
||||||
|
caller: 'support-trpg', // identificativo per log
|
||||||
|
sessionId: session.sessionKey, // tracciabilità sessione
|
||||||
|
productSlug: 'trpg', // attribuzione cost-sdk
|
||||||
|
tenantId: session.tenantId, // per metering tenant
|
||||||
|
sessionType: 'support', // 'support'|'sales'|'gaia'|'demo'|'voice_dialog'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regole**:
|
||||||
|
- `caller` MUST (string)
|
||||||
|
- `sessionId` MUST (string univoca per sessione)
|
||||||
|
- `productSlug` MUST (lowercase code prodotto)
|
||||||
|
- `tenantId` SHOULD (se context tenant disponibile)
|
||||||
|
- `sessionType` SHOULD (per analytics segmentation)
|
||||||
|
- Cost reporter è fire-and-forget (no impact su latency) — vedi `anthropicService.js:96-110`
|
||||||
|
|
||||||
|
**Impatto AGI-4 retrofit**: ogni caller esistente che NON passa `caller+sessionId+productSlug` è non conforme. Fix richiesto in:
|
||||||
|
- `voiceDialogController.js:144-150` — già conforme ✅
|
||||||
|
- `gaiaController.js:580-584,683-685` — già conforme ✅
|
||||||
|
- `publicDemoController.js:171-178,184-189` — già conforme ✅
|
||||||
|
- `sessionService.js:127-133` — **NON conforme** ❌ (`runAgentLoop` chiamato senza opts) → fix in AGI-4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 Quattro sub-template (famiglia AI di prodotto)
|
||||||
|
|
||||||
|
Ogni AI di prodotto deve appartenere a uno dei 4 sub-template. Sub-template definiscono: tool list canonical, KB binding, modello, tone, channel.
|
||||||
|
|
||||||
|
### 4.1 Sub-template "Support post-vendita"
|
||||||
|
|
||||||
|
**Reference implementation**: ARIA_SUPPORT_TRPG ([`supportPrompts.js`](../nexus-ai-ms/src/prompts/supportPrompts.js))
|
||||||
|
|
||||||
|
| Elemento | Valore canonical |
|
||||||
|
|---|---|
|
||||||
|
| Persona | "ARIA" (Aria nel parlato) |
|
||||||
|
| Obiettivo primario | Risolvere il problema dell'utente PRIMA di creare ticket |
|
||||||
|
| Modello | Sonnet (default) |
|
||||||
|
| KB binding | `product_knowledge` cat IN ('FAQ','HOW_TO','TROUBLESHOOT','KNOWN_ISSUE','TRAINING') + PRODUCT_CARD pinned |
|
||||||
|
| Tool list | `search_knowledge_base`, `forward_to_expert`, `escalate_to_ticket` (3) |
|
||||||
|
| Channel | text (canale voice futuro pending) |
|
||||||
|
| FSM | NO |
|
||||||
|
| Tenant context | obbligatorio (tenant esiste post-login) |
|
||||||
|
| Multi-livello KB | L0 + L1 + L2 (filtra `tenant_id`) |
|
||||||
|
| Anti-pollution | si — solo articoli APPROVED + active |
|
||||||
|
|
||||||
|
**Distingue formazione vs bug** (regola business critica):
|
||||||
|
- FORMAZIONE: "non so dove si trova", "come faccio a", ... → `forward_to_expert`
|
||||||
|
- BUG: "da errore", "non funziona", "crash", ... → `escalate_to_ticket`
|
||||||
|
|
||||||
|
### 4.2 Sub-template "Sales pre-vendita"
|
||||||
|
|
||||||
|
**Reference implementation**: Anna su [`publicDemoController.js`](../nexus-ai-ms/src/controllers/publicDemoController.js) (`/public/demo-lead-trpg`)
|
||||||
|
|
||||||
|
| Elemento | Valore canonical |
|
||||||
|
|---|---|
|
||||||
|
| Persona | "Anna" (canonical cross-prodotto) |
|
||||||
|
| Obiettivo primario | Presentare prodotto + qualificare lead + lead capture (email/contatto) |
|
||||||
|
| Modello | Haiku (volume alto, costo critico) |
|
||||||
|
| KB binding | PRODUCT_CARD pinned (obbligatoria) + `getRelevantHelpArticles` cat IN ('HOW_TO','URL_CHUNK','FAQ','DOC_CHUNK') |
|
||||||
|
| Tool list | (base): nessun tool. (demo agent): `show_feature`, `run_macro`, `query_product_state` (3 demo tool) |
|
||||||
|
| Channel | text (chat web) — voice futuro |
|
||||||
|
| FSM | NO |
|
||||||
|
| Tenant context | NO (lead-facing pubblico, no auth) |
|
||||||
|
| Multi-livello KB | SOLO L0 + L1 (NO L2 dati cliente — vincolo GDPR §8) |
|
||||||
|
| Anti-pollution | si + filter `category!='TROUBLESHOOT'` (no info bug interni) |
|
||||||
|
| Lead capture | dopo 4-5 turni: `"Ti mando una demo personalizzata. Come ti contatto?"` |
|
||||||
|
| Demo mode flag | `demo_enabled=true` per attivare 3 demo tool (iframe pilot) |
|
||||||
|
|
||||||
|
### 4.3 Sub-template "Plan-mode dialog approver" (Gaia)
|
||||||
|
|
||||||
|
**Reference implementation**: Gaia TRPG ([`gaiaController.js`](../nexus-ai-ms/src/controllers/gaiaController.js))
|
||||||
|
|
||||||
|
| Elemento | Valore canonical |
|
||||||
|
|---|---|
|
||||||
|
| Persona | "Gaia" (TRPG pilota; nome alternativo per altri prodotti TBD) |
|
||||||
|
| Obiettivo primario | Dialogare con approver umano per raffinare proposta diagnosi ticket PENDING_APPROVAL |
|
||||||
|
| Modello | dinamico: URGENT/HIGH→Opus, default→Sonnet |
|
||||||
|
| KB binding | PRODUCT_CARD pinned + RAG keyword top-3 articoli `product_knowledge` |
|
||||||
|
| Tool list | 11 tool readonly via devenv-gateway: `read_file`, `grep`, `list_dir`, `git_log`, `git_blame`, `run_tests`, `dry_run_sql`, + side-effect: `update_proposal`, `mark_ready`, `request_approver_decision`, `trigger_apply` |
|
||||||
|
| Channel | voice (Charlotte ElevenLabs `XB0fDUnXU5powFXDhCwa` — drift Sarah da chiudere VOX) |
|
||||||
|
| FSM | NO (state machine session in-memory) |
|
||||||
|
| Tenant context | obbligatorio (ticket scoped) |
|
||||||
|
| Multi-livello KB | L0 + L1 + L2 (tenant del ticket) |
|
||||||
|
| Stallo detection | dopo 12 turni / 15 min / 3 request_approver_decision → status `STALLED` + ticket → PENDING_HUMAN_REVIEW |
|
||||||
|
| Anti-loop | max 60 tool call per sessione |
|
||||||
|
| Persistenza | in-memory `sessions Map` (TODO: persistere in `gaia_sessions` per metrics §metrics endpoint) |
|
||||||
|
|
||||||
|
**Caratteristica unica**: ha tool readonly che chiamano `devenv-gateway` (porta 4220 nel container DevEnv prodotto) per ispezionare codice/tests/SQL. Pattern non replicabile direttamente su altri prodotti senza scaffold gateway dedicato.
|
||||||
|
|
||||||
|
### 4.4 Sub-template "Lead qualification verticali"
|
||||||
|
|
||||||
|
**Reference implementation**: Giulia (7 agent EDUCATION, basate su `basePrompts.js` PROMPTS const)
|
||||||
|
|
||||||
|
| Elemento | Valore canonical |
|
||||||
|
|---|---|
|
||||||
|
| Persona | "Giulia" (EDUCATION), "ARIA Health/Legal/Realty" (verticali pending — 0 record DB) |
|
||||||
|
| Obiettivo primario | Qualificare lead tramite FSM verticale-specifica (D1-D23 EDUCATION, H1-H8 HEALTH, L1-L8 LEGAL, R1-R9 REALTY) |
|
||||||
|
| Modello | Sonnet (tool use multi-step) |
|
||||||
|
| KB binding | nessuno (verticali pre-vendita non hanno KB di dominio prodotto) |
|
||||||
|
| Tool list | 5 base FSM: `fsm_transition`, `complete_scoring`, `open_ticket`, `schedule_callback`, `flag_high_priority` |
|
||||||
|
| Channel | text + voice (entrambi configurati con `system_prompt_voice` separato) |
|
||||||
|
| FSM | OBBLIGATORIA (config FSM via `fsmConfigId` foreign key) |
|
||||||
|
| Tenant context | obbligatorio |
|
||||||
|
| VOICE_RULES | OBBLIGATORIE se canale voice |
|
||||||
|
| FSM_RULES | OBBLIGATORIE sempre |
|
||||||
|
|
||||||
|
**Verticali ENUM ammessi** in `ai_agent_profiles.vertical`: `EDUCATION`, `HEALTH`, `LEGAL`, `REALTY` (proposta v1.1: aggiungere `SUPPORT`, `SALES`, `PLAN_MODE`, `META` per coprire altri sub-template — oggi vertical=`SUPPORT` è hack).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 KB binding multi-livello (L0 / L1 / L2 / L3)
|
||||||
|
|
||||||
|
### 5.1 Definizione livelli
|
||||||
|
|
||||||
|
| Livello | Scope | Tenant ID | Esempio TRPG | Owner aggiornamento |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **L0 SYSTEM** | Cross-prodotto, regole AgileHub generali | `tenant_id=1` (system) + `product='AGILEHUB'` | "Cosa è plan vs bypass mode", "Come funzionano i ticket" | Agile AI |
|
||||||
|
| **L1 FIRM** | Per prodotto, dominio applicativo (no dati cliente) | `tenant_id=1` (system) + `product=<PROD>` | TRPG: "Cos'è D.P.R. 151/2011" | Agile AI + esperto verticale prodotto |
|
||||||
|
| **L2 COMPANY** | Per tenant cliente specifico | `tenant_id != 1` + `product=<PROD>` | TRPG Tremolada (`tenant_id=3`): 294 articoli formula bonus | Tenant admin + Agile AI validation |
|
||||||
|
| **L3 PERSONA** ⭐ futuro | Per avatar specifica (vestizione) | `tenant_id != 1` + `product=<PROD>` + `knowledge_filter_slug='<slug>'` | "Anna Tremolada Consulente Senior TRPG" → solo articoli marcati `trpg-tremolada-consulente` | Agile AI + VOX (Lotto F Avatar Registry) |
|
||||||
|
|
||||||
|
### 5.2 PRODUCT_CARD pinned obbligatoria
|
||||||
|
|
||||||
|
Ogni prodotto MUST avere **esattamente 1** record `product_knowledge` con:
|
||||||
|
- `category='PRODUCT_CARD'`
|
||||||
|
- `tenant_id=1` (L1 base)
|
||||||
|
- `active=TRUE`, `review_status='APPROVED'`
|
||||||
|
- `answer` = JSON valido struttura `{description, scope[], target_buyer, free_markdown}`
|
||||||
|
|
||||||
|
**Anti-pollution check** (per audit AGI-4): query
|
||||||
|
```sql
|
||||||
|
SELECT product, COUNT(*) as cards, SUM(JSON_VALID(answer)) as valid_json
|
||||||
|
FROM product_knowledge
|
||||||
|
WHERE category='PRODUCT_CARD' AND active=TRUE
|
||||||
|
GROUP BY product
|
||||||
|
HAVING cards != 1 OR valid_json != cards;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Drift attuale (audit 2026-04-23)**:
|
||||||
|
- AGILEHUB: **3** PRODUCT_CARD (id 720, 729, 730) tutti con `JSON_VALID=0` → fix AGI-4: UPDATE category='HOW_TO' (sono articoli AWE M1/M2/Orchestration mal-categorizzati)
|
||||||
|
- TRPG: 1 PRODUCT_CARD ✅ valido
|
||||||
|
- LG231/ALLTAX/SUSTAINAI/NIS2: **0** PRODUCT_CARD ❌ → fix AGI-4: scaffold scheda base
|
||||||
|
|
||||||
|
### 5.3 Schema `product_knowledge` esteso (proposta migration)
|
||||||
|
|
||||||
|
Schema attuale (verificato Hetzner 2026-04-23):
|
||||||
|
```
|
||||||
|
id, tenant_id, product, category, question, answer, tags JSON, keywords,
|
||||||
|
source ENUM, source_type ENUM, source_url, source_file_path, source_source_id,
|
||||||
|
content_hash, fetched_at, chunk_index, chunk_total,
|
||||||
|
learned_from_session_id, learned_from_ticket_id, expert_id,
|
||||||
|
review_status ENUM, views, helpful, language, active, created_at, updated_at
|
||||||
|
```
|
||||||
|
|
||||||
|
**Migration proposta v1.0 → v1.1** (NON applicare in v1.0, sub-approval separata):
|
||||||
|
```sql
|
||||||
|
ALTER TABLE product_knowledge
|
||||||
|
ADD COLUMN kb_level ENUM('L0','L1','L2','L3') NULL DEFAULT NULL AFTER product,
|
||||||
|
ADD COLUMN knowledge_filter_slug VARCHAR(64) NULL DEFAULT NULL AFTER kb_level,
|
||||||
|
ADD INDEX idx_kb_level (kb_level),
|
||||||
|
ADD INDEX idx_knowledge_filter (knowledge_filter_slug);
|
||||||
|
```
|
||||||
|
|
||||||
|
Backfill `kb_level`:
|
||||||
|
- `tenant_id=1 AND product='AGILEHUB'` → `L0`
|
||||||
|
- `tenant_id=1 AND product!='AGILEHUB'` → `L1`
|
||||||
|
- `tenant_id!=1 AND knowledge_filter_slug IS NULL` → `L2`
|
||||||
|
- `tenant_id!=1 AND knowledge_filter_slug IS NOT NULL` → `L3` (futuro Lotto F)
|
||||||
|
|
||||||
|
### 5.4 Retrieval keyword (oggi) → Voyage AI embedding (futuro)
|
||||||
|
|
||||||
|
**Oggi** (LIVE produzione):
|
||||||
|
- `getRelevantHelpArticles(product, query, K)` ([`productKbSnippet.js`](../nexus-ai-ms/src/services/productKbSnippet.js))
|
||||||
|
- Tokenizzazione naive: split query in token >3 char, scoring `LIKE %token%` su `keywords` (peso 3) + `question` (peso 2) + `answer` (peso 1)
|
||||||
|
- Order by `score DESC, views DESC LIMIT K`
|
||||||
|
- **Limiti**: nessun semantic match (sinonimi, riformulazioni), no re-ranking, latency dipende da MySQL
|
||||||
|
|
||||||
|
**Roadmap v1.1+** (decision Agile AI + utente):
|
||||||
|
- **Opzione A**: Voyage AI embedding service ($0.10/M token) → vector column → cosine similarity
|
||||||
|
- **Opzione B**: rimanere keyword + aggiungere stemming italiano + sinonimi manuali
|
||||||
|
- **Opzione C**: Claude Haiku re-ranking layer (cross-encoder LLM) sul top-N keyword
|
||||||
|
- **Trigger decisione**: quando articoli per prodotto > 100 con KB binding multi-tenant attivo
|
||||||
|
|
||||||
|
### 5.5 Anti-pollution principle
|
||||||
|
|
||||||
|
Memoria persistente `feedback_no_demo_data_production.md`:
|
||||||
|
> Articoli `product_knowledge` seedati con contenuti fuori scope inquinano RAG ranking.
|
||||||
|
|
||||||
|
**Regole vincolanti**:
|
||||||
|
1. NO articoli demo/test in produzione: usare `product='TEST_DEMO'` per testing
|
||||||
|
2. NO PRODUCT_CARD multipli per stesso `(tenant_id, product)`
|
||||||
|
3. NO PRODUCT_CARD con `JSON_VALID(answer)=0`
|
||||||
|
4. NO articoli con `language` diverso dal default tenant senza override esplicito
|
||||||
|
5. Spot check periodico Agile AI (almeno mensile + post AGI-4 retrofit)
|
||||||
|
|
||||||
|
### 5.6 L3 PERSONA forward-looking (vestizione avatar)
|
||||||
|
|
||||||
|
Concetto introdotto sessione 33 part 4 (visione utente Avatar Registry):
|
||||||
|
|
||||||
|
```
|
||||||
|
AVATAR TECNICO + VESTIZIONE = PERSONA PUBBLICA
|
||||||
|
(voice_id + (display_name + visibile cliente
|
||||||
|
replica_id) knowledge_id + "Anna Tremolada
|
||||||
|
system_prompt + Consulente Senior")
|
||||||
|
cost_per_minute)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementazione Lotto F (settimane 2-3 master plan v2.1)** — coordinata con VOX Lotto A foundation:
|
||||||
|
|
||||||
|
1. Migration `ai_profiles` (nexus-presenter-ms) ADD COLUMN `knowledge_id VARCHAR(64) NULL` (slug logico filtro KB)
|
||||||
|
2. Migration `product_knowledge` ADD COLUMN `knowledge_filter_slug VARCHAR(64) NULL` (vedi §5.3)
|
||||||
|
3. Modify `getRelevantHelpArticles` per accettare `knowledge_filter_slug` opzionale: `WHERE (knowledge_filter_slug IS NULL OR knowledge_filter_slug = ?)`
|
||||||
|
4. Ranking weight per livello: L0=0.5, L1=1.0, L2=1.5, L3=2.0
|
||||||
|
5. Seed esempi:
|
||||||
|
- Avatar "Anna Generica" → `knowledge_id='agilehub-default'`
|
||||||
|
- Avatar "Anna Tremolada Consulente TRPG" → `knowledge_id='trpg-tremolada-consulente'`
|
||||||
|
- Articoli AGILEHUB esistenti → marcare `knowledge_filter_slug='agilehub-default'`
|
||||||
|
- Articoli TRPG L2 Tremolada → marcare `knowledge_filter_slug='trpg-tremolada-consulente'`
|
||||||
|
|
||||||
|
**Coordinamento**:
|
||||||
|
- VOX: schema `ai_profiles` esteso (Lotto A) + voice integration
|
||||||
|
- MAESTRO: blocchi AWE `D02_LoadAvatarKB(slug)` + `C09_RetrieveFromKB(query, knowledge_id, livello)`
|
||||||
|
- PRISMA: UI tab agenti `/admin/agents` con KB binding selector (Lotto C, approval gate)
|
||||||
|
|
||||||
|
**Status v1.0**: documentato come `PROPOSED` — implementazione conforme richiesta da v1.1 dopo deploy Lotto F.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 Tool list standard
|
||||||
|
|
||||||
|
### 6.1 Catalogo NEXUS_TOOLS (11 tool)
|
||||||
|
|
||||||
|
Da [`nexusTools.js`](../nexus-ai-ms/src/tools/nexusTools.js):
|
||||||
|
|
||||||
|
| # | Tool | Family | Side-effect | Eseguito da |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | `fsm_transition` | FSM | Sì (FSM state) | nexus-fsm-ms via `ToolExecutor.fsmTransition` |
|
||||||
|
| 2 | `complete_scoring` | FSM | Sì (lead score) | nexus-lead-ms (futuro; oggi log) |
|
||||||
|
| 3 | `open_ticket` | FSM | Sì (ticket new) | nexus-ticket-ms (futuro; oggi log) |
|
||||||
|
| 4 | `schedule_callback` | FSM | Sì (calendario) | log only oggi |
|
||||||
|
| 5 | `flag_high_priority` | FSM | Sì (lead flag) | log only oggi |
|
||||||
|
| 6 | `search_knowledge_base` | Support | NO (read-only) | nexus-ticket-ms `/product-knowledge/search` |
|
||||||
|
| 7 | `forward_to_expert` | Support | Sì (expert notification) | nexus-ticket-ms `/support-sessions/:id/forward-expert` |
|
||||||
|
| 8 | `escalate_to_ticket` | Support | Sì (ticket new) | nexus-ticket-ms `/support-sessions/:id/escalate` |
|
||||||
|
| 9 | `show_feature` | Demo | Sì (iframe message) | client postMessage via `demoActions[]` accumulator |
|
||||||
|
| 10 | `run_macro` | Demo | Sì (iframe message) | client postMessage via `demoActions[]` accumulator |
|
||||||
|
| 11 | `query_product_state` | Demo | NO (read-only) | placeholder MVP — futuro live state pull |
|
||||||
|
|
||||||
|
### 6.2 Tool list per famiglia (matrice sub-template × tool)
|
||||||
|
|
||||||
|
| Tool | Support | Sales | Plan-mode | Lead qualif |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `fsm_transition` | NO | NO | NO | **MUST** |
|
||||||
|
| `complete_scoring` | NO | NO | NO | **MUST** |
|
||||||
|
| `open_ticket` | NO | SHOULD (lead capture) | NO | SHOULD |
|
||||||
|
| `schedule_callback` | NO | SHOULD | NO | SHOULD |
|
||||||
|
| `flag_high_priority` | NO | NO | NO | SHOULD |
|
||||||
|
| `search_knowledge_base` | **MUST** | NO | NO | NO |
|
||||||
|
| `forward_to_expert` | **MUST** | NO | NO | NO |
|
||||||
|
| `escalate_to_ticket` | **MUST** | NO | NO | NO |
|
||||||
|
| `show_feature` | NO | SHOULD se `demo_enabled` | NO | NO |
|
||||||
|
| `run_macro` | NO | SHOULD se `demo_enabled` | NO | NO |
|
||||||
|
| `query_product_state` | NO | SHOULD se `demo_enabled` | NO | NO |
|
||||||
|
| `read_file`/`grep`/`list_dir`/`git_log`/`git_blame` (custom Gaia) | NO | NO | **MUST se Plan-mode** | NO |
|
||||||
|
| `run_tests`/`dry_run_sql` (custom Gaia) | NO | NO | SHOULD se Plan-mode | NO |
|
||||||
|
| `update_proposal`/`mark_ready`/`request_approver_decision`/`trigger_apply` (custom Gaia) | NO | NO | **MUST se Plan-mode** | NO |
|
||||||
|
|
||||||
|
### 6.3 Estensioni custom (Gaia readonly via devenv-gateway)
|
||||||
|
|
||||||
|
Pattern Gaia (TRPG pilota, [`gaiaController.js:228-250`](../nexus-ai-ms/src/controllers/gaiaController.js)):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const PRODUCT_GATEWAYS = {
|
||||||
|
TRPG: {
|
||||||
|
url: process.env.TRPG_GATEWAY_URL || 'http://trpg-pro-agile-devenv:4220',
|
||||||
|
key: process.env.TRPG_GATEWAY_KEY || process.env.DEVENV_GATEWAY_KEY,
|
||||||
|
displayName: 'TRPG',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function callGateway(product, toolName, args) {
|
||||||
|
const gw = PRODUCT_GATEWAYS[product];
|
||||||
|
// POST a http://<gateway>/tools/<toolName> con X-Internal-Key
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regole estensione custom tool**:
|
||||||
|
- Tool con prefisso `read_*`/`grep`/`list_*`/`git_*` SHOULD essere readonly
|
||||||
|
- Tool con side-effect su prodotto target richiedono devenv-gateway dedicato per quel prodotto
|
||||||
|
- Spec contratto separato (es. `docs/SPEC_DEVENV_GATEWAY_TOOLS.md` futuro)
|
||||||
|
- Audit obbligatorio (vedi `gaia_audit_log` per Gaia)
|
||||||
|
|
||||||
|
### 6.4 Cost estimation per tool (D6 metering)
|
||||||
|
|
||||||
|
Tool che usano LLM hanno cost esplicito tracciato via cost-sdk. Tool che usano solo HTTP/DB → cost trascurabile, no metering.
|
||||||
|
|
||||||
|
Esempio Gaia (per session):
|
||||||
|
- LLM cost: cumulato via `runAgentLoop` → `result.totalUsage.{inputTokens,outputTokens}` × pricing modello
|
||||||
|
- Tool readonly cost: trascurabile (HTTP locale)
|
||||||
|
- Tool `dry_run_sql`: trascurabile (snapshot readonly DB)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §7 Guardrails minimi obbligatori
|
||||||
|
|
||||||
|
### 7.1 Pattern canonical LIMITI ASSOLUTI
|
||||||
|
|
||||||
|
Sezione 7 system_prompt deve sempre presente, formato:
|
||||||
|
```
|
||||||
|
LIMITI ASSOLUTI:
|
||||||
|
- Non <azione vietata 1>
|
||||||
|
- Non <azione vietata 2>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Limiti minimi per famiglia
|
||||||
|
|
||||||
|
**Support post-vendita**:
|
||||||
|
- Non inventare funzionalità non presenti nella KB
|
||||||
|
- Non chiedere informazioni tecniche complesse (log, stack trace) — quello lo farà il team tecnico
|
||||||
|
- Non superare 6 scambi senza proporre `forward_to_expert` o `escalate_to_ticket`
|
||||||
|
- Se la KB non ha risultati, dirlo onestamente
|
||||||
|
|
||||||
|
**Sales pre-vendita**:
|
||||||
|
- Non inventare feature non presenti nella scheda
|
||||||
|
- Non garantire prezzi non confermati ("dipende dal volume — te lo vedo dopo")
|
||||||
|
- Non promettere SLA/disponibilità senza verifica
|
||||||
|
- Non sminuire prodotti competitor (etica commerciale)
|
||||||
|
- Tool MAI proattivamente — solo su richiesta esplicita utente (regola FERREA `demo_enabled`)
|
||||||
|
|
||||||
|
**Plan-mode dialog approver (Gaia)**:
|
||||||
|
- Non modificare mai il codice sorgente del prodotto (tool readonly only)
|
||||||
|
- Non eseguire SQL non-SELECT (`dry_run_sql` enforced backend)
|
||||||
|
- Ammettere quando non si sa
|
||||||
|
- Non chiamare `trigger_apply` in altre situazioni che non sia approvazione esplicita approver
|
||||||
|
|
||||||
|
**Lead qualification verticali**:
|
||||||
|
- Non garantire posti/disponibilità senza verifica
|
||||||
|
- Non indicare prezzi non confermati
|
||||||
|
- Non promettere esiti concorsi/graduatorie/cause/vendite (per verticale)
|
||||||
|
- (HEALTH) Non formulare diagnosi, non suggerire farmaci
|
||||||
|
- (LEGAL) Non dare consulenza legale personalizzata, non citare articoli di legge specifici
|
||||||
|
- (REALTY) Non garantire prezzi/valutazioni senza sopralluogo
|
||||||
|
|
||||||
|
### 7.3 Limiti universali (cross-famiglia, MUST sempre)
|
||||||
|
|
||||||
|
- Rispondere SEMPRE in italiano (default; `en` se tenant lo richiede)
|
||||||
|
- Rispettare scoping multi-tenant (mai rivelare dati altri tenant)
|
||||||
|
- Non superare il limite turni configurato (anti-loop)
|
||||||
|
- Non inventare valori KB ("preferisco verificare col team — ti rispondo se mi lasci un contatto")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §8 GDPR — split AI prodotto vs AI marketing
|
||||||
|
|
||||||
|
### 8.1 Principio
|
||||||
|
|
||||||
|
Memoria persistente `project_ai_prodotto_vs_marketing.md`: due cervelli AI separati per GDPR compliance.
|
||||||
|
|
||||||
|
| Tipo AI | Dati accessibili | Audience | Hosting |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **AI prodotto** (server-side) | L0 + L1 + L2 (dati cliente reali) | utenti autenticati del cliente | post-login, scoped tenant_id |
|
||||||
|
| **AI marketing** (pubblico) | L0 + L1 SOLO `category IN ('PRODUCT_CARD','HOW_TO','URL_CHUNK')` filtrati pubblici | lead anonimi su landing | pre-login, no tenant context |
|
||||||
|
|
||||||
|
### 8.2 Implementazione filtro
|
||||||
|
|
||||||
|
Per **AI Sales pre-vendita** (publicDemoController):
|
||||||
|
```sql
|
||||||
|
WHERE product = ?
|
||||||
|
AND tenant_id = 1 -- L0/L1 only
|
||||||
|
AND category IN ('PRODUCT_CARD','HOW_TO','URL_CHUNK','FAQ','DOC_CHUNK')
|
||||||
|
AND review_status = 'APPROVED'
|
||||||
|
AND active = TRUE
|
||||||
|
AND (language IS NULL OR language = ?)
|
||||||
|
```
|
||||||
|
|
||||||
|
Per **AI Support post-vendita / Plan-mode** (autenticati):
|
||||||
|
```sql
|
||||||
|
WHERE product = ?
|
||||||
|
AND (tenant_id = 1 OR tenant_id = ?) -- L0/L1 + L2 cliente
|
||||||
|
AND review_status = 'APPROVED'
|
||||||
|
AND active = TRUE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 GDPR Right to be Forgotten
|
||||||
|
|
||||||
|
- DELETE articoli L2 con `tenant_id = <cliente_da_dimenticare>`: cascade + audit log
|
||||||
|
- L3 PERSONA (futuro): DELETE articoli con `knowledge_filter_slug = '<cliente>-<persona>'`
|
||||||
|
- Endpoint dedicato: `DELETE /ai/products/:product/knowledge?tenant_id=X&confirm=YES_GDPR`
|
||||||
|
- Backup retention: 30 giorni dopo delete (compliance retention)
|
||||||
|
|
||||||
|
### 8.4 Anti-leak vincolante
|
||||||
|
|
||||||
|
- AI marketing NEVER deve restituire articoli con `tenant_id != 1`
|
||||||
|
- AI prodotto NEVER deve restituire articoli di altri `tenant_id` rispetto al chiamante
|
||||||
|
- Log query KB con `tenant_id` filter applicato (per audit GDPR)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §9 Cost tracking MUST (cost-sdk integration)
|
||||||
|
|
||||||
|
### 9.1 Pattern obbligatorio
|
||||||
|
|
||||||
|
Vedi §3.12. Chiamata canonical:
|
||||||
|
```js
|
||||||
|
const result = await anthropicService.chat(messages, systemPrompt, tools, {
|
||||||
|
model: anthropicService.MODELS.SONNET,
|
||||||
|
caller: 'support-trpg', // MUST
|
||||||
|
sessionId: session.sessionKey, // MUST
|
||||||
|
productSlug: 'trpg', // MUST
|
||||||
|
tenantId: session.tenantId, // SHOULD
|
||||||
|
sessionType: 'support', // SHOULD
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Anti-pattern (NON conforme AGI-4)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// BAD: opts mancanti
|
||||||
|
const result = await anthropicService.chat(messages, systemPrompt, tools);
|
||||||
|
// ^ 4° arg vuoto
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Esempio TRPG conforme
|
||||||
|
|
||||||
|
Da `voiceDialogController.js:144-150`:
|
||||||
|
```js
|
||||||
|
const result = await anthropicService.chat(messages, systemPrompt, null, {
|
||||||
|
model: anthropicService.MODELS.HAIKU,
|
||||||
|
caller: 'voice-dialog',
|
||||||
|
sessionId,
|
||||||
|
productSlug: (ticket?.product ? String(ticket.product).toLowerCase() : 'agilehub'),
|
||||||
|
sessionType: 'voice_dialog',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Da `gaiaController.js:683-685`:
|
||||||
|
```js
|
||||||
|
{ model: preferredModel, caller: `gaia:${priority}`, sessionId: session.sessionId,
|
||||||
|
productSlug: (session.product || 'agilehub').toLowerCase(), sessionType: 'gaia' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 Cost reporter implementation
|
||||||
|
|
||||||
|
Vedi `anthropicService.js:6-23` + `shared-libraries/cost-sdk` integration:
|
||||||
|
- Fire-and-forget (no impact su latency)
|
||||||
|
- Endpoint: `POST {HUB_URL}/api/cost/log` (nexus-hub-ms Fase 2 LIVE)
|
||||||
|
- Visibility dashboard: `/admin/cost?month=YYYY-MM` (admin_tenant role)
|
||||||
|
|
||||||
|
### 9.5 Telemetria di base (anche senza cost-sdk)
|
||||||
|
|
||||||
|
Pattern `anthropicService.js:93-95` (sempre attivo se `opts.caller || AI_USAGE_LOG=1`):
|
||||||
|
```js
|
||||||
|
logger.info(`[ai-usage] caller=${opts.caller} model=${model} in=${usage.inputTokens} out=${usage.outputTokens}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
Quindi `caller` è MUST anche per debug/log baseline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §10 Conformance checklist (per audit AGI-4 retrofit)
|
||||||
|
|
||||||
|
### 10.1 Matrice 12 elementi × MUST/SHOULD
|
||||||
|
|
||||||
|
Per ogni AI di prodotto (tabella `ai_agent_profiles` + caller controllers):
|
||||||
|
|
||||||
|
| # | Elemento | Verifica | Fix se mancante |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | Identità persona | `ai_agent_profiles.name` valorizzato + system_prompt riga 1 = "Sei <Nome>" | UPDATE `name` + UPDATE `system_prompt` (header) |
|
||||||
|
| 2 | Obiettivo dichiarato | `system_prompt` contiene "IL TUO OBIETTIVO" o equivalente | UPDATE `system_prompt` aggiungere sezione 2 |
|
||||||
|
| 3 | Modello Claude esplicito | grep caller per `opts.model` | Modificare caller con `opts.model: MODELS.X` |
|
||||||
|
| 4 | System prompt strutturato 7 sezioni | `LENGTH(system_prompt) > 800` AND contiene marker sezioni | UPDATE `system_prompt` con template canonical |
|
||||||
|
| 5 | KB binding | sub-template Support/Sales/Plan-mode → caller chiama `getProductCardSnippet` + `getRelevantHelpArticles` | Modificare caller con KB inject |
|
||||||
|
| 6 | Tool list dichiarativa | sub-template !=Lead generico → tool list non vuota nel caller | Modificare caller passando tools |
|
||||||
|
| 7 | LIMITI ASSOLUTI | `system_prompt` contiene "LIMITI ASSOLUTI" + N bullet | UPDATE `system_prompt` aggiungere sezione 7 |
|
||||||
|
| 8 | VOICE_RULES (se voice) | `system_prompt_voice` contiene `VOICE_RULES` text | UPDATE `system_prompt_voice` |
|
||||||
|
| 9 | FSM_RULES (se Lead) | `vertical IN ('EDUCATION','HEALTH','LEGAL','REALTY')` → `system_prompt` contiene `FSM_RULES` | UPDATE `system_prompt` |
|
||||||
|
| 10 | Channel-aware | se canale voice supportato → `system_prompt_voice IS NOT NULL` | UPDATE `system_prompt_voice` |
|
||||||
|
| 11 | Tenant context | caller chiama `buildSystemPrompt(vertical, channel, tenantConfig)` con tenantConfig valorizzato | Modificare caller passare tenantConfig |
|
||||||
|
| 12 | Cost tracking MUST | `caller` + `sessionId` + `productSlug` in ogni `anthropicService.{chat,runAgentLoop}` call | Modificare caller passando opts |
|
||||||
|
|
||||||
|
### 10.2 Score conformance
|
||||||
|
|
||||||
|
Per ogni AI di prodotto:
|
||||||
|
- 12/12 MUST + tutti SHOULD applicabili → **CONFORME** ✅
|
||||||
|
- 12/12 MUST, alcuni SHOULD mancanti → **CONFORME PARZIALE** ⚠️ (warning, no block)
|
||||||
|
- < 12 MUST → **NON CONFORME** ❌ (bloccante AGI-4 sign-off)
|
||||||
|
|
||||||
|
### 10.3 Procedura audit AGI-4 per prodotto
|
||||||
|
|
||||||
|
1. Agile AI esegue audit checklist → produce `docs/AGI4_GAP_<PRODUCT>.md`
|
||||||
|
2. Gap report contiene: matrice 12 elementi compilata, fix proposti per ogni gap, effort stimato
|
||||||
|
3. Sub-approval esplicita utente per applicare fix
|
||||||
|
4. Apply (modifiche `nexus-ai-ms/src/...`) — pattern §10.3 team roster (modifiche file di prodotto AgileHub stesso, OK)
|
||||||
|
5. Smoke test: 3-5 query reali AI prodotto post-fix
|
||||||
|
6. Aggiornamento `ai_agent_profiles` + commit + sub-approval push Gitea
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §11 "Agile" — AI runtime di AgileHub (caso speciale)
|
||||||
|
|
||||||
|
### 11.1 Scope deciso (sessione 33 part 6, decisione utente AGI-2 domanda 4)
|
||||||
|
|
||||||
|
**Entrambi (full)**: una sola "Agile" AI con context routing.
|
||||||
|
|
||||||
|
| Context | Trigger | System prompt | Tool list | KB |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Admin AgileHub** | Chiamata da pagine `/admin/*` autenticato role `admin_tenant`/`admin_global` | "Agile per amministratori" — governance tone | `query_master_plan`, `query_standard`, `query_agent_status`, `query_workflow_runs`, `query_cost`, `search_kb_agilehub` | L0 AGILEHUB + L1 ADMIN |
|
||||||
|
| **Utenti finali prodotti** | Chiamata da widget cross-prodotto / pagine `/help`-style | "Agile per utenti" — assistente tone | `search_kb_agilehub`, `escalate_to_support` | L0 AGILEHUB + L1 USER (subset pubblico) |
|
||||||
|
|
||||||
|
### 11.2 Naming canonical
|
||||||
|
|
||||||
|
- `display_name`: "Agile"
|
||||||
|
- `agent_key`: `AGILE_RUNTIME` (in `ai_agent_profiles`)
|
||||||
|
- `vertical`: nuovo ENUM `META` (proposta v1.1 schema migration)
|
||||||
|
|
||||||
|
### 11.3 System prompt template "Agile"
|
||||||
|
|
||||||
|
```
|
||||||
|
Sei Agile, l'assistente AI di AgileHub.
|
||||||
|
|
||||||
|
[Context routing — admin]
|
||||||
|
Stai parlando con un amministratore di AgileHub. Aiutalo a:
|
||||||
|
- Conoscere lo stato del sistema (master plan settimana corrente, agenti specialisti, standard cross-suite, workflow runs AWE, cost mese corrente)
|
||||||
|
- Trovare articoli nella KB AGILEHUB
|
||||||
|
- Suggerire azioni concrete (es. "vuoi consultare REGENT per arbitrato?")
|
||||||
|
|
||||||
|
[Context routing — utente finale]
|
||||||
|
Stai parlando con un utente di un prodotto AgileHub. Aiutalo a:
|
||||||
|
- Capire come usare AgileHub (workflow editor, dashboard, mobile)
|
||||||
|
- Trovare guide nella KB AGILEHUB
|
||||||
|
- Escalare a support se serve
|
||||||
|
|
||||||
|
[KB inject: top-K articoli AGILEHUB rilevanti]
|
||||||
|
[Tool list: query_* per admin, search/escalate per user]
|
||||||
|
[LIMITI ASSOLUTI: non rivelare info di altri tenant; non inventare feature non presenti in KB]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.4 Modello Claude
|
||||||
|
|
||||||
|
- Default: **Sonnet** (dialogo + tool use)
|
||||||
|
- Greeting / messaggio singolo: Haiku
|
||||||
|
- Reasoning profondo (es. "perché workflow run è failed?"): Opus on-demand
|
||||||
|
|
||||||
|
### 11.5 KB binding
|
||||||
|
|
||||||
|
- L0 AGILEHUB: 52 articoli esistenti (ground truth audit 2026-04-23) — id 720+
|
||||||
|
- L1 ADMIN/USER: split via nuovo campo `audience ENUM('admin','user','all')` in product_knowledge (proposta v1.1 schema)
|
||||||
|
- L3 PERSONA: `display_name='Agile Admin'` vs `display_name='Agile Helper'` con stessi voice_id ma KB filter differente (futuro Lotto F)
|
||||||
|
|
||||||
|
### 11.6 Implementazione AGI-3 (~5-6gg)
|
||||||
|
|
||||||
|
Step:
|
||||||
|
1. INSERT `ai_agent_profiles` record `AGILE_RUNTIME` con system_prompt template
|
||||||
|
2. Nuovo controller `agileController.js` con context routing logic
|
||||||
|
3. Tool dispatcher: `query_master_plan`, `query_standard`, ecc. (5-7 tool nuovi)
|
||||||
|
4. Endpoint `/ai/agile/chat` con auth detection (role admin → admin context)
|
||||||
|
5. Widget UI in `/admin/agile` (Agile chat panel — coordinamento PRISMA approval gate)
|
||||||
|
6. Smoke test: 5 query admin + 5 query user
|
||||||
|
7. Cost tracking conformance MUST
|
||||||
|
|
||||||
|
**Dipendenze AGI-3**:
|
||||||
|
- AGILE AI design tool list + system prompt template (questo doc + estensione)
|
||||||
|
- MAESTRO co-owner workflow integration (chiamare blocco AWE da Agile)
|
||||||
|
- PRISMA reviewer UI exposure (`/admin/agile` panel)
|
||||||
|
- Sub-approval utente prima del deploy LIVE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §12 Stato adoption per prodotto (snapshot 2026-04-23)
|
||||||
|
|
||||||
|
### 12.1 Tabella matrice prodotto × elementi standard
|
||||||
|
|
||||||
|
Audit DB Hetzner 2026-04-23 (`SELECT product, count, has_card, kb_levels FROM ...`):
|
||||||
|
|
||||||
|
| Prodotto | Tot articoli KB | PRODUCT_CARD | KB livelli presenti | ai_agent_profiles | Score conformance | Note |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| **AGILEHUB** | 52 | **3 (pollution)** ❌ | L0 only | 0 ❌ | NON CONFORME | Fix AGI-4: UPDATE 2 PRODUCT_CARD → HOW_TO; creare AGILE_RUNTIME (AGI-3) |
|
||||||
|
| **TRPG** | 295 | 1 ✅ | L1 (1) + L2 (294 Tremolada) | 1 (ARIA_SUPPORT_TRPG) ⚠️ short prompt | CONFORME PARZIALE | Fix: estendere `system_prompt` ARIA_SUPPORT_TRPG a template completo; aggiungere ANNA_SALES_TRPG + GAIA_PLANMODE_TRPG in DB |
|
||||||
|
| **TRPG-PRO** | 0 | 0 ❌ | nessuno | 0 ❌ | NON CONFORME | Fix: scaffolding L1 base + ai_agent_profiles ARIA_SUPPORT_TRPG_PRO |
|
||||||
|
| **SUSTAINAI** | 27 | 0 ❌ | L1 (1) + L2 (26 cliente 5) | 1 (ARIA_SUPPORT_SUSTAINAI) ⚠️ short prompt | NON CONFORME | Fix: scaffolding PRODUCT_CARD + estendere system_prompt |
|
||||||
|
| **NIS2** | 2 | 0 ❌ | L1 (1) + L2 (1 cliente 7) | 1 (ARIA_SUPPORT_NIS2) ⚠️ short prompt | NON CONFORME | Fix: scaffolding KB completo + PRODUCT_CARD |
|
||||||
|
| **AllTax (TAXAI)** | 83 | 0 ❌ | L2 only (cliente 6) | 1 (ARIA_SUPPORT_ALLTAX) ⚠️ short prompt | NON CONFORME | Fix: scaffolding L1 base + PRODUCT_CARD |
|
||||||
|
| **LG231** | 91 | 0 ❌ | L1 (1) + L2 (90 cliente 8) | 1 (ARIA_SUPPORT_LG231) ⚠️ short prompt | NON CONFORME | Fix: scaffolding PRODUCT_CARD + estendere system_prompt |
|
||||||
|
| **DFM-PRO** | 0 | 0 ❌ | nessuno | 0 ❌ | NON CONFORME | Scope completo retrofit AGI-4 |
|
||||||
|
| **ALLRISK** | 0 | 0 ❌ | nessuno | 0 ❌ | NON CONFORME | Scope completo retrofit AGI-4 |
|
||||||
|
| **WMS** | 0 | 0 ❌ | nessuno | 0 ❌ | NON CONFORME | Scope completo retrofit AGI-4 |
|
||||||
|
| **MADEBYCLOUD** | 0 | 0 ❌ | nessuno | 0 ❌ | NON CONFORME | Fuori scope AGI-4 master plan v2.1 |
|
||||||
|
| **CertiSource** | 0 | 0 ❌ | nessuno | 0 ❌ | NON CONFORME | Fuori scope AGI-4 master plan v2.1 |
|
||||||
|
|
||||||
|
### 12.2 Drift cross-prodotto da chiudere
|
||||||
|
|
||||||
|
| Drift | Affected | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| `system_prompt` ARIA_SUPPORT troppo corto (220-300 char vs template ~3000 char) | TRPG, SUSTAINAI, ALLTAX, NIS2, LG231 | AGI-4: UPDATE `system_prompt` con `buildSupportPrompt(<PRODUCT>)` rendered text |
|
||||||
|
| `system_prompt_voice` NULL su tutti ARIA_SUPPORT | tutti | AGI-4: popolare `system_prompt_voice` se voice supportato |
|
||||||
|
| Anna Sales non in `ai_agent_profiles` (hardcoded controller) | tutti i prodotti con landing demo | AGI-4: INSERT `ANNA_SALES_<PRODUCT>` per ogni prodotto con landing |
|
||||||
|
| Gaia Plan-mode in-memory (no record DB) | TRPG (pilota) | AGI-4 (post AGI-5): persistere config in `ai_agent_profiles` `GAIA_PLANMODE_TRPG` + tabella `gaia_sessions` per metrics |
|
||||||
|
| 7 Giulia EDUCATION non hanno fsm_config_id valorizzato (`has_fsm=0` query DB) | tutti i 7 Giulia | AGI-4: link FSM config |
|
||||||
|
| Cost tracking opzionale in `sessionService.js:127` | tutti i caller `runAgentLoop` legacy | AGI-4: passare opts cost-sdk |
|
||||||
|
|
||||||
|
### 12.3 Priorità retrofit AGI-4
|
||||||
|
|
||||||
|
Settimane 3-4 master plan v2.1 (~7-10gg):
|
||||||
|
|
||||||
|
| Settimana | Prodotti | Effort |
|
||||||
|
|---|---|---|
|
||||||
|
| 3 (8-14/5) | AGILEHUB (fix pollution + creare Agile AGI-3) | 5gg |
|
||||||
|
| 3-4 (12-21/5) | TRPG estendere ARIA_SUPPORT + creare ANNA_SALES + GAIA_PLANMODE | 2gg |
|
||||||
|
| 4 (15-21/5) | SUSTAINAI + NIS2 + ALLTAX + LG231 (fix existing ARIA_SUPPORT + scaffold PRODUCT_CARD) | 3gg |
|
||||||
|
| 5 (22-28/5) | DFM-PRO + ALLRISK + WMS (scope completo) | 4gg |
|
||||||
|
| Backlog v1.1 | MADEBYCLOUD + CertiSource | TBD |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §13 Cronologia versioni
|
||||||
|
|
||||||
|
| Data | Versione | Modifica | Owner |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 2026-04-23 | 1.0 (proposed) | Creazione standard. Sub-template Support + Sales + Plan-mode + Lead qualif. KB multi-livello L0/L1/L2 + L3 PERSONA forward-looking. Cost tracking MUST. Naming convention netto (Agile vs Agile AI vs Anna vs Gaia). Conformance checklist 12 elementi. Tabella adoption snapshot 12 prodotti. | Agile AI (AGI-2 master plan v2.1) |
|
||||||
|
| _(future)_ | 1.1 | Migration `kb_level` + `knowledge_filter_slug` + `audience` columns. ENUM vertical esteso (`META`, `SUPPORT`, `SALES`, `PLAN_MODE`). L3 PERSONA promosso da `PROPOSED` a STANDARD. Embedding service decision. | TBD |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Documento mantenuto da**: Agile AI (Architetto AI di prodotto + KB governance)
|
||||||
|
**Distribuzione**: in attesa sub-approval utente per seed `nexus_hub.hub_standards` slug=`ai-prodotto` v1.0 + distribuzione cross-suite (pattern `installer-integration`)
|
||||||
|
**Riferimento operativo**: [`docs/AGENT_AGILE_AI.md`](AGENT_AGILE_AI.md) v1.0 + [`docs/BRIEFING_AGILE_AI.md`](BRIEFING_AGILE_AI.md) v1.0
|
||||||
|
**Master Plan link**: [`docs/MASTER_PLAN_GO_LIVE_4_WEEKS.md`](MASTER_PLAN_GO_LIVE_4_WEEKS.md) v2.1 sezione AGI-1...AGI-6
|
||||||
246
docs/STANDARD_EMAIL_RELAY.md
Normal file
246
docs/STANDARD_EMAIL_RELAY.md
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
# STANDARD AgileHub — Email Relay centralizzato (canonico)
|
||||||
|
|
||||||
|
> **Autorità**: AgileHub (single source of truth per la suite Agile Software).
|
||||||
|
> **Versione**: 1.0 — 2026-04-21
|
||||||
|
> **Stato**: ADOTTATO. Vincolante per tutti i prodotti della suite.
|
||||||
|
> **Destinatari**: team TRPG, SustainAI, NIS2, TAXAI (AllTax), LG231, DFM, MKTG, ALLRISK, WMS, MADEBYCLOUD, CertiSource.
|
||||||
|
> **Registry DB**: record master in `nexus_hub.hub_standards` (slug=`email-relay`, version=`1.0`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Principio
|
||||||
|
|
||||||
|
**Tutte le email generate dalla suite AgileHub passano da UN SOLO punto**: il microservizio `email-automation-ms` (PM2 id 21, porta 4004).
|
||||||
|
|
||||||
|
Nessun prodotto, microservizio, container DevEnv, cron o agent AI può accedere direttamente a:
|
||||||
|
- Postfix host Hetzner (`172.18.0.1:25`)
|
||||||
|
- Server Gmail (`smtp-relay.gmail.com`, `smtp.gmail.com`)
|
||||||
|
- Altri provider SMTP (SendGrid, Mailgun, AWS SES, ecc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architettura
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐ HTTPS + X-Internal-Key
|
||||||
|
│ Prodotto cliente │──────────────┐
|
||||||
|
│ (TRPG/SustAI...) │ │
|
||||||
|
└──────────────────┘ ▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
┌──────────────────┐ │ email-automation-ms │ SMTP (internal)
|
||||||
|
│ Microservizio │────▶│ porta 4004 │──────────┐
|
||||||
|
│ AgileHub │ │ │ ▼
|
||||||
|
│ (ticket/lead/..) │ │ Rate limit + Template│ ┌──────────────┐
|
||||||
|
└──────────────────┘ │ Audit DB + Retry │ │ Postfix host │
|
||||||
|
└──────────────────────┘ │ 172.18.0.1:25│
|
||||||
|
└──────┬───────┘
|
||||||
|
│ SASL auth
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ Gmail │
|
||||||
|
│ smtp-relay │
|
||||||
|
│ .gmail.com │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Relay chain**:
|
||||||
|
- Prodotto → `email-automation-ms:4004` (via Apache vhost `agilehub.agile.software/api/emails/*`)
|
||||||
|
- `email-automation-ms` → Postfix `172.18.0.1:25` (container→host, senza auth perché `mynetworks` include `172.18.0.0/16`)
|
||||||
|
- Postfix → Gmail SMTP relay `:587` (richiede SASL auth service-account Workspace)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Contratto API
|
||||||
|
|
||||||
|
### Endpoint canonico
|
||||||
|
|
||||||
|
```
|
||||||
|
POST https://agilehub.agile.software/api/emails/send
|
||||||
|
Headers:
|
||||||
|
X-Internal-Key: <INTERNAL_EMAIL_KEY> (shared via /etc/agilehub/internal-keys.env)
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
Body:
|
||||||
|
{
|
||||||
|
"to": "user@example.com", // string o array
|
||||||
|
"from_tenant_id": 5, // int — attribuzione log
|
||||||
|
"template": "demo-registration-verify", // nome template in email-automation-ms/templates/
|
||||||
|
"variables": { "name": "Mario", ... }, // placeholder per il template
|
||||||
|
"product": "TRPG", // slug prodotto (audit)
|
||||||
|
"reply_to": "support@trpg.agile.software", // opzionale
|
||||||
|
"priority": "transactional" // "transactional" | "marketing" | "system"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message_id": "<agilehub-7c3f-20260421@smtp-relay.gmail.com>",
|
||||||
|
"queued_at": "2026-04-21T15:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log audit
|
||||||
|
|
||||||
|
Ogni invio viene registrato in `nexus_email_db.email_log`:
|
||||||
|
- `id, message_id, to, from, subject, template, product, tenant_id, status, sent_at, smtp_response, retry_count`
|
||||||
|
|
||||||
|
### Status delivery
|
||||||
|
|
||||||
|
- `queued` — accettato dal service, in coda retry
|
||||||
|
- `sent` — accettato dal Postfix host (risposta 250 OK)
|
||||||
|
- `delivered` — accettato dal Gmail relay (webhook o check log mail.log)
|
||||||
|
- `bounced` — Gmail ha rifiutato (bad recipient, spam, auth failure)
|
||||||
|
- `failed_permanent` — 5xx, no retry
|
||||||
|
- `failed_transient` — 4xx, retry backoff (max 3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. VIETATO
|
||||||
|
|
||||||
|
Qualsiasi alternativa al percorso `email-automation-ms`. In particolare:
|
||||||
|
|
||||||
|
❌ **Connessione SMTP diretta** a `172.18.0.1:25` da container prodotto o DevEnv.
|
||||||
|
❌ **Provider cloud** (SendGrid, Mailgun, SES, Postmark) — se necessari, integrarli DENTRO `email-automation-ms` come alternative transport.
|
||||||
|
❌ **Librerie SMTP client** in codice prodotto:
|
||||||
|
- JS: `nodemailer`, `emailjs`, `smtp-connection`
|
||||||
|
- PHP: `PHPMailer`, `Swift_Mailer`, `mail()` built-in, `mb_send_mail()`
|
||||||
|
- Python: `smtplib` usato con credenziali cleartext
|
||||||
|
- Ruby, Go, ecc.: equivalenti
|
||||||
|
❌ **Binari di sistema** in container: `sendmail`, `mail`, `mutt`, `msmtp`.
|
||||||
|
❌ **File .env con `SMTP_*`** in qualsiasi servizio diverso da `email-automation-ms`.
|
||||||
|
|
||||||
|
### Deroghe
|
||||||
|
|
||||||
|
Solo via ticket AgileHub tag `email-bypass-exception`. Motivazioni accettabili:
|
||||||
|
- Integrazione legacy in dismissione con data di fine (es. mktg-agile oggi)
|
||||||
|
- Volume massimo > 100k/mese che giustifica provider dedicato (richiede integrazione in email-automation-ms, non bypass)
|
||||||
|
- Test E2E isolati (usa container mailpit dedicato, non produzione)
|
||||||
|
|
||||||
|
Deroghe hanno scadenza obbligatoria (max 90gg). AgileHub tiene registro in `nexus_hub.hub_standards_adoption.exemption_reason/exemption_expires_at`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Come integrare
|
||||||
|
|
||||||
|
### Lato prodotto (TRPG, SustainAI, ecc.)
|
||||||
|
|
||||||
|
1. Rimuovere ogni `SMTP_*` dal `.env` del prodotto
|
||||||
|
2. Aggiungere `INTERNAL_EMAIL_KEY=<value>` al `.env` (coordinato con team AgileHub)
|
||||||
|
3. Sostituire chiamate dirette con `curl`/`fetch`/`requests` verso `https://agilehub.agile.software/api/emails/send`
|
||||||
|
4. Rimuovere dipendenze SMTP (`nodemailer`, `PHPMailer`, ecc.) dal manifest (`package.json`, `composer.json`)
|
||||||
|
|
||||||
|
### Lato AgileHub
|
||||||
|
|
||||||
|
1. `email-automation-ms` espone `/send` + `/templates/list` + `/webhooks/bounce`
|
||||||
|
2. Gestisce rate limit per tenant (`nexus_email_db.email_quota`)
|
||||||
|
3. Tiene in vita la connessione SMTP a Postfix (connection pool)
|
||||||
|
4. Retry exponential backoff su 4xx (1min, 5min, 30min), drop su 5xx
|
||||||
|
5. Webhook bounce da Gmail (future) → aggiorna `status=bounced` + notifica tenant admin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Stato attuale (2026-04-22 — FIX APPLICATO)
|
||||||
|
|
||||||
|
### ✅ Operativo end-to-end
|
||||||
|
|
||||||
|
- Architettura centralizzata implementata e funzionante
|
||||||
|
- IP allowlist Workspace attiva per `135.181.149.254` (configurata da Massimo Tagliavini)
|
||||||
|
- `sendmail` locale: funziona (cron watchdog, claude-auth-check, ticket-agent)
|
||||||
|
- **`email-automation-ms` canonical path**: funziona dopo fix 2026-04-22
|
||||||
|
- Endpoint protetti da `X-Internal-Key` (auth middleware aggiunto)
|
||||||
|
- Env vars propagate in `/etc/agilehub/internal-keys.env` + `.env` di 5 prodotti produzione
|
||||||
|
|
||||||
|
### Fix applicato al transport Nodemailer
|
||||||
|
|
||||||
|
File: `email-automation-ms/src/services/emailService.js:25-40`
|
||||||
|
```javascript
|
||||||
|
transporter = nodemailer.createTransport({
|
||||||
|
host, // da SMTP_HOST=127.0.0.1 (era 172.18.0.1)
|
||||||
|
port,
|
||||||
|
secure,
|
||||||
|
name: process.env.SMTP_HELO_NAME || 'agile.software', // HELO esplicito
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
File: `email-automation-ms/.env`
|
||||||
|
```
|
||||||
|
SMTP_HOST=127.0.0.1 # era 172.18.0.1 (docker bridge) → Postfix vedeva connessione "esterna"
|
||||||
|
INTERNAL_SERVICE_KEY=nexus-internal-2026 # alias INTERNAL_EMAIL_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auth endpoint
|
||||||
|
|
||||||
|
File: `email-automation-ms/src/index.js:27-55` — middleware `requireInternalKey`:
|
||||||
|
- Protegge: `/emails/send`, `/emails/send-raw`, `/sequences/*`, `/emails/templates/db` (POST/PUT/DELETE)
|
||||||
|
- Legge la chiave da `INTERNAL_EMAIL_KEY` (preferita) o `INTERNAL_SERVICE_KEY` (retrocompat cron)
|
||||||
|
- Senza header → 401 UNAUTHORIZED
|
||||||
|
- Con header valido → passa al controller
|
||||||
|
- Se nessuna chiave configurata → warn + pass (fallback legacy)
|
||||||
|
|
||||||
|
### Test E2E superati (2026-04-22)
|
||||||
|
|
||||||
|
| Test | Risultato |
|
||||||
|
|------|-----------|
|
||||||
|
| POST `/emails/send-raw` senza X-Internal-Key | 401 ✅ |
|
||||||
|
| POST `/emails/send-raw` con X-Internal-Key (locale) | 201 + messageId + SENT in DB ✅ |
|
||||||
|
| POST HTTPS `https://agilehub.agile.software/api/emails/send-raw` da container TRPG | 201 + SENT in DB, product=TRPG ✅ |
|
||||||
|
| Consegna su Gmail `250 2.0.0 OK` via `smtp-relay.gmail.com:587` | ✅ confermato in `/var/log/mail.log` |
|
||||||
|
|
||||||
|
### ❌ Bypass noti
|
||||||
|
|
||||||
|
- `/var/www/mktg-agile/.env` contiene ancora `SMTP_*` — tollerato perché prodotto in dismissione Fase C (spegnimento 2026-04-21+)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Obblighi prodotti della suite
|
||||||
|
|
||||||
|
Per essere conforme a `email-relay` v1.0 ogni prodotto **DEVE**:
|
||||||
|
|
||||||
|
1. ✅ Rimuovere ogni `SMTP_*`, credenziali Gmail, API key mailer-cloud dal proprio `.env`
|
||||||
|
2. ✅ Eliminare dipendenze mailer (nodemailer, PHPMailer, ecc.) dal manifest
|
||||||
|
3. ✅ Sostituire ogni invio con POST verso `https://agilehub.agile.software/api/emails/send`
|
||||||
|
4. ✅ Ottenere `INTERNAL_EMAIL_KEY` da AgileHub team (via ticket tag `email-key-provision`)
|
||||||
|
5. ✅ Aggiungere `INTERNAL_EMAIL_KEY` al `.env` istanza con chmod 600
|
||||||
|
6. ✅ Usare template definiti in `email-automation-ms/templates/` (aprire PR per nuovi template)
|
||||||
|
7. ✅ Loggare `message_id` ricevuto nella response per debug
|
||||||
|
|
||||||
|
Un prodotto non conforme entra in `pending` adoption nel registry. Deroghe formali con scadenza obbligatoria.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Riferimenti
|
||||||
|
|
||||||
|
- Microservizio: `/var/www/agile-services/email-automation-ms/` (Hetzner) + `/projects/agile-services/email-automation-ms/` (container)
|
||||||
|
- Entry point: `email-automation-ms/src/index.js:45` (SMTP transport config)
|
||||||
|
- Template repo: `email-automation-ms/templates/` (Handlebars-like .hbs)
|
||||||
|
- Log DB: `nexus_email_db.email_log` (MySQL host Hetzner)
|
||||||
|
- Postfix config: `/etc/postfix/main.cf` (Hetzner host)
|
||||||
|
- Gmail Workspace Admin Console: account `admin@agile.software`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Changelog
|
||||||
|
|
||||||
|
### v1.2 — 2026-04-22 (fix applicato — operativo)
|
||||||
|
- Fix transport Nodemailer: `SMTP_HOST=127.0.0.1` (era `172.18.0.1` docker bridge) + `name: 'agile.software'` HELO esplicito
|
||||||
|
- Aggiunto middleware `requireInternalKey` su `/emails/send*`, `/sequences/*`, `/emails/templates/db` mutazioni — chiave `INTERNAL_SERVICE_KEY=nexus-internal-2026` (alias `INTERNAL_EMAIL_KEY`)
|
||||||
|
- Registrato `INTERNAL_EMAIL_KEY` in `/etc/agilehub/internal-keys.env`
|
||||||
|
- Propagato `INTERNAL_EMAIL_KEY` + `EMAIL_MS_URL` al `.env` di 5 prodotti produzione (trpg-agile, sustainai-agile, nis2-agile, mktg-agile, allrisk-agile). Altri 5 prodotti (trpg-pro-agile, taxai-agile, lg231-agile, dfm-pro-agile, wms-agile) non hanno `.env` in produzione — useranno la registry centrale.
|
||||||
|
- Test E2E passati: invio da container TRPG via HTTPS verso `agilehub.agile.software/api/emails/send-raw` → consegnato Gmail 250 OK.
|
||||||
|
|
||||||
|
### v1.1 — 2026-04-22 (correzione narrativa, pre-fix)
|
||||||
|
- Correzione narrativa sezione 6: SMTP NON è bloccato. Il path `sendmail` funziona (Massimo ha configurato IP allowlist da tempo). Il bug reale era in `email-automation-ms` transport.
|
||||||
|
- Rimosso "fix A/B Postfix SASL" (errore di analisi del 2026-04-21 — non serviva).
|
||||||
|
|
||||||
|
### v1.0 — 2026-04-21
|
||||||
|
- Versione iniziale — formalizza architettura esistente
|
||||||
|
- Distribuita a 11 prodotti suite via DB registry `hub_standards`
|
||||||
|
- Richiesta conformità prodotti: rimuovere SMTP diretti dove presenti
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Firma autorità**: AgileHub. Violazioni vanno in `hub_standards_distribution_log` con `result='non_compliant'`.
|
||||||
298
docs/STANDARD_INSTALLER_INTEGRATION.md
Normal file
298
docs/STANDARD_INSTALLER_INTEGRATION.md
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# STANDARD AgileHub — Installer Integration (canonico)
|
||||||
|
|
||||||
|
> **Autorità**: AgileHub (single source of truth per la suite Agile Software).
|
||||||
|
> **Versione**: 1.0.1 — 2026-04-19 (patch: chiarimento payload tenants/report)
|
||||||
|
> **Stato**: ADOTTATO. Sostituisce qualsiasi proposta prodotto in caso di conflitto.
|
||||||
|
> **Destinatari**: team TRPG, SustainAI, NIS2, TAXAI (AllTax), LG231, DFM, MKTG, ALLRISK, WMS, MADEBYCLOUD, CertiSource.
|
||||||
|
> **Spec origine**: `AGILEHUB_INSTALLER_INTEGRATION_SPEC.md` v1.0 (TRPG, 2026-04-19) — recepita con override sotto.
|
||||||
|
> **Registry DB**: record master in `nexus_hub.hub_standards` (slug=`installer-integration`, version=`1.0.1`). Questo file è una **copia derivata** — in caso di disallineamento vince il DB.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.0.1 — 2026-04-19 (patch, non breaking)
|
||||||
|
- **§3.1 `POST /api/tenants/report`**: chiarito che `license_key` è **obbligatorio** nel body (era implicito via "firma HMAC obbligatoria" ma non listato nel payload d'esempio). Serve al middleware HMAC per il lookup della `hub_signing_key`. Origine: smoke test TRPG 2026-04-19 21:15 — 404 LICENSE_NOT_FOUND senza license_key. Decisione AgileHub: consistency > minimal payload (defence in depth anti-tamper).
|
||||||
|
- TRPG già conforme via commit `5c1aa2e` (2026-04-19).
|
||||||
|
- Altri prodotti: allineare payload `tenants/report` aggiungendo `license_key` string.
|
||||||
|
|
||||||
|
### v1.0 — 2026-04-19
|
||||||
|
- Versione iniziale adottata. Spec origine TRPG recepita con override AgileHub.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Portata e autorità
|
||||||
|
|
||||||
|
Questo documento definisce **il contratto unico** fra AgileHub e i prodotti installati on-premise (o white-label) della suite. Vale per:
|
||||||
|
|
||||||
|
- Registrazione prodotti in AgileHub (`hub_products`)
|
||||||
|
- Provisioning istanze cliente (orchestrator SSH + installer script prodotto)
|
||||||
|
- Licensing + heartbeat + alert
|
||||||
|
- Onboarding tenant (consulting_firms) cross-istanza
|
||||||
|
- Firma HMAC + vault SSH
|
||||||
|
|
||||||
|
**Regola di precedenza**: in caso di conflitto fra una proposta prodotto (es. `*_INTEGRATION_SPEC.md` nel loro `docs/`) e questo standard, **vince questo standard**. Deviazioni motivate vanno chieste via ticket AgileHub con tag `standard-exception`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Decisioni AgileHub (override sulla spec TRPG v1.0)
|
||||||
|
|
||||||
|
### 2.1 Hostname ufficiale
|
||||||
|
|
||||||
|
| Ruolo | Hostname | Uso |
|
||||||
|
|-------|----------|-----|
|
||||||
|
| Hub UI | `https://agilehub.agile.software` | Dashboard admin, `/admin/products/*` |
|
||||||
|
| Hub API (licensing) | `https://agilehub.agile.software/api/license/*` | endpoint validate/activate/register/report |
|
||||||
|
| Hub API (installer orchestrator) | `https://agilehub.agile.software/api/installer/*` | wizard, job status, log streaming |
|
||||||
|
|
||||||
|
> **NO `hub.agile.software`**. I prodotti che chiamano `hub.agile.software` vanno corretti prima del pilota.
|
||||||
|
|
||||||
|
### 2.2 Stack tecnico
|
||||||
|
|
||||||
|
| Layer | Scelta |
|
||||||
|
|-------|--------|
|
||||||
|
| Backend licensing/installer | **Node.js 20 + Express** (pattern `nexus-*-ms`), microservizio nuovo `nexus-hub-ms` porta **4217** |
|
||||||
|
| DB | **MySQL host Hetzner** (172.18.0.1:3306), DB dedicato **`nexus_hub`**, user `nexus_user` |
|
||||||
|
| Frontend | **Next.js 15 + React 19** in `nexus-dashboard`, nuova sezione `/admin/products/*` |
|
||||||
|
| Job queue (Fase 3) | **BullMQ su Redis** (da aggiungere infra). Fino a Fase 2: cron + status polling |
|
||||||
|
| Email | Microservizio esistente **`email-automation-ms`** (NO SendGrid/MailHog esterni) |
|
||||||
|
| Secret vault | **AES-256-GCM env-based**, chiave in `/etc/agilehub/vault.env` (NO HashiCorp Vault/KMS) |
|
||||||
|
| SSH worker | **ssh2** (npm) invocato da `nexus-hub-ms`, chiave decifrata on-demand |
|
||||||
|
|
||||||
|
### 2.3 Prefisso tabelle e naming DB
|
||||||
|
|
||||||
|
- DB dedicato: `nexus_hub`
|
||||||
|
- Tabelle: prefisso `hub_` mantenuto (compat con spec origine). Accesso esclusivo da `nexus-hub-ms`.
|
||||||
|
- Cross-DB query verso `nexus_ticket_db`, `nexus_tenant_db`, `nexus_lead_db` sono permesse solo via GRANT dedicato a `nexus_user` (pattern già in uso per SSO).
|
||||||
|
|
||||||
|
### 2.4 Ruoli
|
||||||
|
|
||||||
|
| Ruolo | Permessi |
|
||||||
|
|-------|----------|
|
||||||
|
| `hub_admin` | Tutto tranne provisioning SSH |
|
||||||
|
| `hub_installer` | **Nuovo**. Lettura `hub_*`, esecuzione wizard, decrypt chiavi SSH con audit obbligatorio |
|
||||||
|
| `hub_support` | Solo lettura `hub_instances`, `hub_tenants`, `hub_license_heartbeats` |
|
||||||
|
|
||||||
|
Il ruolo `hub_installer` è **richiesto** per Fase 3. Non riutilizzare `hub_admin`: blast radius troppo ampio.
|
||||||
|
|
||||||
|
### 2.5 Versioning
|
||||||
|
|
||||||
|
Recepita la regola TRPG: **version mai hardcodata nei seed o nelle migration**. La hub la registra da payload heartbeat. Per `hub_products.version` (ultima release disponibile) leggere on-demand via `GET https://<product>.agile.software/version.json` oppure aggiornare via `POST /api/products/:slug/version` (admin-only).
|
||||||
|
|
||||||
|
### 2.6 Integrazione obbligatoria con SSO
|
||||||
|
|
||||||
|
`hub_tenants.owner_email` **DEVE** essere foreign-key logica verso `sso_identities.email` (DB `nexus_tenant_db`). Al provisioning di un nuovo tenant:
|
||||||
|
|
||||||
|
1. `nexus-hub-ms` POST `/api/auth/admin/tenants/create` sull'istanza
|
||||||
|
2. L'istanza crea `consulting_firm` + `user` locale
|
||||||
|
3. `nexus-hub-ms` replica l'identità in `sso_identities` (se non esiste)
|
||||||
|
4. Prima login dell'owner → SSO flow standard (Fase 2 SSO)
|
||||||
|
|
||||||
|
**Prodotti che non hanno ancora SSO dual-mode**: finché non è attivo, `hub_tenants` funziona stand-alone ma `owner_email` deve comunque essere univoco cross-istanza.
|
||||||
|
|
||||||
|
### 2.7 Deprecazione `products.json`
|
||||||
|
|
||||||
|
`/opt/agent-ai/hub/products.json` è **deprecato** dalla Fase 2. Entro la Fase 2 completa:
|
||||||
|
- `hub_products` diventa la sorgente di verità
|
||||||
|
- La UI legge da `hub_products`, non più da `products.json`
|
||||||
|
- `products.json` resta come fallback statico per landing page esterne, generato da cron da `hub_products`
|
||||||
|
|
||||||
|
### 2.8 Multi-prodotto da subito
|
||||||
|
|
||||||
|
Fase 1 va implementata **generica** (non TRPG-specific). Lo schema, gli endpoint e le firme HMAC supportano `product_slug` variabile fin dal primo giorno. Il seed iniziale è per TRPG, ma il codice non hardcoda nulla.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Contratto API canonico
|
||||||
|
|
||||||
|
### 3.1 Endpoint hub (Istanza → AgileHub)
|
||||||
|
|
||||||
|
Tutti su `https://agilehub.agile.software/api/...`, header obbligatorio:
|
||||||
|
```
|
||||||
|
X-Product-Signature: hmac_sha256(body, HUB_SIGNING_KEY_<PRODUCT_SLUG_UPPER>)
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
| Endpoint | Scopo | Chiamato da |
|
||||||
|
|----------|-------|-------------|
|
||||||
|
| `POST /api/license/activate` | Prima attivazione dopo install | `install-<slug>.sh` |
|
||||||
|
| `POST /api/license/validate` | Heartbeat periodico (24h default, 1h se anomalia) | cron istanza |
|
||||||
|
| `POST /api/instances/register` | Registrazione finale istanza | `install-<slug>.sh` |
|
||||||
|
| `POST /api/tenants/report` | Sync tenant create/update/archive | istanza, on-change |
|
||||||
|
| `GET /api/products/:slug/version` | Ultima versione disponibile | `update-<slug>.sh` |
|
||||||
|
|
||||||
|
Payload: identici alla spec TRPG §6 (validate/activate/register/report). Campi obbligatori aggiuntivi in **tutti** i body:
|
||||||
|
- `product_slug` — sempre, anche dove la spec TRPG lo omette implicitamente
|
||||||
|
- `license_key` — **sempre** (v1.0.1 clarification). Serve al middleware HMAC per lookup di `hub_signing_key` prima della verifica firma. Include `tenants/report` che nella spec origine non lo listava esplicitamente.
|
||||||
|
|
||||||
|
**Eccezione**: `POST /api/license/activate` non porta `X-Product-Signature` (primo handshake, la chiave non esiste ancora) ma deve comunque includere `license_key` per il match iniziale.
|
||||||
|
|
||||||
|
### 3.2 Endpoint istanza (AgileHub → Istanza)
|
||||||
|
|
||||||
|
Ogni prodotto della suite **deve** esporre:
|
||||||
|
|
||||||
|
| Endpoint | Scopo | Auth |
|
||||||
|
|----------|-------|------|
|
||||||
|
| `POST /api/auth/admin/tenants/create` | Onboarding tenant da hub | Bearer JWT service + `X-Hub-Signature` |
|
||||||
|
| `POST /api/auth/admin/tenants/:id/suspend` | Sospensione tenant | idem |
|
||||||
|
| `POST /api/auth/admin/tenants/:id/resume` | Riattivazione tenant | idem |
|
||||||
|
| `GET /api/auth/admin/health` | Health check completo per hub | API key service |
|
||||||
|
|
||||||
|
**Shape risposta**: identica alla spec TRPG §5.2. Ogni prodotto implementa il proprio endpoint ma lo shape è vincolante.
|
||||||
|
|
||||||
|
### 3.3 Installer script non-interattivo (obbligatorio per prodotti orchestrati)
|
||||||
|
|
||||||
|
Ogni prodotto della suite che vuole supportare provisioning orchestrato da AgileHub **deve** fornire uno script:
|
||||||
|
|
||||||
|
```
|
||||||
|
install-<slug>.sh --non-interactive --json-output \
|
||||||
|
--slug=... --domain=... --client-name=... --admin-email=... --admin-name=... \
|
||||||
|
--license-key=... --hub-url=https://agilehub.agile.software \
|
||||||
|
--<provider-key-1>=... --<provider-key-2>=... \
|
||||||
|
[--smtp-host=... ...] [--force-reinstall]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exit codes standard** (identici per tutti i prodotti):
|
||||||
|
|
||||||
|
| Codice | Significato |
|
||||||
|
|--------|-------------|
|
||||||
|
| 0 | Success |
|
||||||
|
| 10 | Preflight failed (docker, RAM, disk, porte) |
|
||||||
|
| 20 | Secret generation failed |
|
||||||
|
| 30 | Hub activation failed (license invalid) |
|
||||||
|
| 40 | Docker load/up failed |
|
||||||
|
| 50 | Migration DB failed |
|
||||||
|
| 60 | Seed/admin creation failed |
|
||||||
|
| 70 | SSL setup failed |
|
||||||
|
| 80 | Verify-install failed |
|
||||||
|
| 90 | Internal error |
|
||||||
|
|
||||||
|
**JSON output**: shape identica a spec TRPG §5.1. Campo `product_slug` obbligatorio in entrambi i rami (success/failure).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Schema DB canonico (DB `nexus_hub`)
|
||||||
|
|
||||||
|
Recepito integralmente dallo schema TRPG §3.1 (8 tabelle):
|
||||||
|
|
||||||
|
- `hub_products`
|
||||||
|
- `hub_instances`
|
||||||
|
- `hub_instance_provisioning_logs`
|
||||||
|
- `hub_licenses`
|
||||||
|
- `hub_license_heartbeats`
|
||||||
|
- `hub_license_alerts`
|
||||||
|
- `hub_tenants`
|
||||||
|
- `hub_ssh_credentials`
|
||||||
|
|
||||||
|
**Modifiche AgileHub**:
|
||||||
|
|
||||||
|
1. `hub_products.framework` VARCHAR(32) NULL — aggiunto, indica stack prodotto (`php-vanilla`, `node-express`, `nextjs`, ecc.) utile all'orchestrator
|
||||||
|
2. `hub_instances.sso_enabled` BOOLEAN DEFAULT FALSE — aggiunto, indica se il prodotto ha SSO attivo su quell'istanza
|
||||||
|
3. `hub_tenants.sso_identity_id` INT NULL — FK logica a `sso_identities.id` (riempito quando SSO attivo)
|
||||||
|
4. Tutte le tabelle hanno `tenant_id INT NULL` per compat futuro con multi-tenancy hub (oggi NULL, riservato)
|
||||||
|
|
||||||
|
Migration canonical: `nexus-hub-ms/migrations/001-initial-schema.sql` (da creare). **Sub-approval obbligatoria** prima dell'esecuzione su produzione (maintenance mode come da CLAUDE.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Sicurezza
|
||||||
|
|
||||||
|
### 5.1 Firma HMAC
|
||||||
|
|
||||||
|
- **Algoritmo**: HMAC-SHA256
|
||||||
|
- **Chiave**: `HUB_SIGNING_KEY_<PRODUCT_SLUG_UPPER>` (es. `HUB_SIGNING_KEY_TRPG`, `HUB_SIGNING_KEY_SUSTAINAI`)
|
||||||
|
- **Generazione**: `openssl rand -hex 32`
|
||||||
|
- **Storage**: in `/etc/agilehub/vault.env` sull'host AgileHub + `.env` di ogni istanza (propagata da installer al momento di `activate`)
|
||||||
|
- **Rotazione**: ogni 6 mesi, bidirezionale coordinata
|
||||||
|
|
||||||
|
### 5.2 SSH vault
|
||||||
|
|
||||||
|
- Cifratura: AES-256-GCM
|
||||||
|
- Master key: `HUB_VAULT_KEY` in `/etc/agilehub/vault.env` (NON in DB, NON in repo git)
|
||||||
|
- Ogni decrypt logga in `hub_audit_logs` con `user_id`, `timestamp`, `source_ip`, `purpose`
|
||||||
|
- Sudo whitelist sul server target: file `/etc/sudoers.d/<slug>-installer` con binari strettamente necessari
|
||||||
|
|
||||||
|
### 5.3 JWT service-to-service
|
||||||
|
|
||||||
|
- Algoritmo: RS256
|
||||||
|
- Chiave privata AgileHub + chiave pubblica distribuita ai prodotti al momento dell'attivazione
|
||||||
|
- Claim obbligatori: `iss=agilehub`, `aud=<product_slug>`, `role=service`, `exp` ≤ 5 minuti
|
||||||
|
- Ogni prodotto verifica `iss` + `aud` + `exp` + firma
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Fasi di rollout (coordinate cross-prodotto)
|
||||||
|
|
||||||
|
| Fase | Durata | Chi fa cosa | Bloccante per |
|
||||||
|
|------|--------|-------------|---------------|
|
||||||
|
| **Fase 1** — API licensing hub | 2-3 gg | AgileHub: `nexus-hub-ms` + migration `nexus_hub` + 5 endpoint + seed 11 prodotti | Prima installazione cliente qualsiasi prodotto |
|
||||||
|
| **Fase 1b** — Installer script prodotto | 3-5 gg/prodotto | Ogni prodotto: `install-<slug>.sh` non-interattivo + `update-<slug>.sh` + `/api/auth/admin/tenants/*` endpoint | Pilota white-label di quel prodotto |
|
||||||
|
| **Fase 2** — UI Prodotti read-only | 3-4 gg | AgileHub: sidebar "Prodotti", elenco, detail read-only, heartbeat dashboard | Visibilità pilota |
|
||||||
|
| **Fase 3** — Orchestrator + wizard | 6-8 gg | AgileHub: wizard 5-step, SSH worker, log streaming, rollback, BullMQ, encrypt vault | Onboarding cliente self-service |
|
||||||
|
| **Fase 4** — Multi-prodotto generalizzato | 2-3 gg | AgileHub: template wizard per prodotto, `installer_orchestrator_config` drive-based, billing hook | Scale-up suite |
|
||||||
|
|
||||||
|
**Pilota**: TRPG primo. Dopo pilota OK, checklist per onboarding secondo prodotto (SustainAI probabile next).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Obblighi prodotti della suite
|
||||||
|
|
||||||
|
Per essere "orchestrabile" da AgileHub, un prodotto **DEVE**:
|
||||||
|
|
||||||
|
1. ✅ Fornire `install-<slug>.sh --non-interactive --json-output` con exit codes e shape JSON canonici
|
||||||
|
2. ✅ Fornire `update-<slug>.sh` (Fase 3, no urgenza) con stesso pattern
|
||||||
|
3. ✅ Esporre `GET /version.json` con `{version, released_at, changelog_url}`
|
||||||
|
4. ✅ Esporre `GET /api/auth/admin/health` autenticato
|
||||||
|
5. ✅ Esporre `POST /api/auth/admin/tenants/create|:id/suspend|:id/resume` con HMAC + JWT service
|
||||||
|
6. ✅ Chiamare `POST /api/license/activate` alla fine di `install-<slug>.sh`
|
||||||
|
7. ✅ Avere un cron `license-heartbeat.sh` che chiama `POST /api/license/validate` ogni 24h (1h se anomalia)
|
||||||
|
8. ✅ Chiamare `POST /api/tenants/report` ad ogni create/update/archive consulting_firm
|
||||||
|
9. ✅ Gestire stati licenza: `valid → read_only (48h grace) → locked (168h)` con banner UI maintenance
|
||||||
|
10. ✅ Propagare `HUB_SIGNING_KEY_<SLUG>` nel `.env` istanza al momento dell'attivazione
|
||||||
|
|
||||||
|
Un prodotto che **non** rispetta questi 10 punti non può fare parte del catalogo installer AgileHub. Può restare nel menu `products.json` come link esterno (`installer_type=external_link`) finché non si adegua.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Domande aperte — risposte ufficiali
|
||||||
|
|
||||||
|
Le 10 domande di §11 della spec TRPG trovano risposta nel capitolo 2 di questo doc. Riepilogo:
|
||||||
|
|
||||||
|
1. **Framework hub**: Node.js/Express (non PHP).
|
||||||
|
2. **DB**: MySQL host Hetzner, DB dedicato `nexus_hub`.
|
||||||
|
3. **Job queue**: cron fino a Fase 2, BullMQ/Redis da Fase 3.
|
||||||
|
4. **UI**: Next.js 15 + React 19 (nexus-dashboard).
|
||||||
|
5. **Vault SSH**: AES-256-GCM env-based.
|
||||||
|
6. **Monitoring**: `/health` endpoint + cron watchdog esistente. Prometheus opzionale in Fase 4.
|
||||||
|
7. **Ruolo installer**: nuovo `hub_installer` separato da `hub_admin`.
|
||||||
|
8. **Email**: `email-automation-ms` esistente.
|
||||||
|
9. **Multi-prodotto**: generico da Fase 1, seed TRPG ma codice agnostic.
|
||||||
|
10. **Billing**: nessun Stripe oggi. `hub_customers` placeholder, billing in Fase 4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Prossimi passi
|
||||||
|
|
||||||
|
1. **AgileHub**: scaffold `nexus-hub-ms` + migration `001-initial-schema.sql` (sub-approval richiesta)
|
||||||
|
2. **AgileHub**: distribuire questo documento a tutti i prodotti della suite via SSH Hetzner
|
||||||
|
3. **TRPG**: adeguare `install-trpg.sh` al contratto §3.3 (hostname `agilehub.agile.software`, exit codes canonici, JSON shape)
|
||||||
|
4. **TRPG**: implementare `/api/auth/admin/tenants/*` endpoint con HMAC + JWT (§3.2)
|
||||||
|
5. **Suite**: ogni team leggere questo doc e aprire ticket AgileHub con tag `installer-readiness` per dichiarare quando saranno pronti
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Riferimenti
|
||||||
|
|
||||||
|
- Spec origine TRPG: `/var/www/trpg-agile/docs/AGILEHUB_INSTALLER_INTEGRATION_SPEC.md` (v1.0, 2026-04-19)
|
||||||
|
- Roadmap TRPG: `/var/www/trpg-agile/docs/INSTALLER_ROADMAP.md`
|
||||||
|
- White-label TRPG: `/var/www/trpg-agile/docs/INSTALLER_WHITELABEL.md`
|
||||||
|
- Licensing model TRPG: `/var/www/trpg-agile/docs/PROTEZIONE_FASE4_LICENSING.md`
|
||||||
|
- Multi-studio TRPG: `/var/www/trpg-agile/docs/MULTI_STUDIO_SPEC.md`
|
||||||
|
- SSO AgileHub: `/projects/agile-services/docs/SPEC_SSO_SINGLE_SIGN_ON.md`
|
||||||
|
- SSO fasi: `/projects/agile-services/docs/SSO_FASI_IMPLEMENTAZIONE.md`
|
||||||
|
- CLAUDE.md AgileHub: `/projects/agile-services/CLAUDE.md` (regole maintenance + agent + escalation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Firma autorità**: questo documento è **canonico** per la suite Agile Software. Modifiche richiedono consenso AgileHub + notifica a tutti i prodotti della suite.
|
||||||
|
|
||||||
|
**Versione**: 1.0 — 2026-04-19
|
||||||
|
**Prossima revisione**: dopo completamento pilota TRPG (stimata 2026-05-31).
|
||||||
237
docs/STANDARD_MARKETING_TENANT_PROVISIONING.md
Normal file
237
docs/STANDARD_MARKETING_TENANT_PROVISIONING.md
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
# Standard `marketing-tenant-provisioning` v1.4
|
||||||
|
|
||||||
|
**Owner**: AgileHub MARKETER agent (governance) + TITAN (esecuzione DKIM) + MAESTRO (orchestratore atomico AC30)
|
||||||
|
**Stato**: adopted (LIVE 2026-04-26 mattina v1.3 — DKIM per-tenant; v1.4 mattina — AC30 pronto in branch, deploy pending trigger)
|
||||||
|
**Applies to**: `*` (tutti i prodotti suite — TRPG, SUSTAINAI, NIS2, LG231, TAXAI, DFM, MKTG, ALLRISK, WMS, MADEBYCLOUD, AGILEHUB)
|
||||||
|
**Versione**: 1.4
|
||||||
|
**MS implementatore**: `nexus-marketing-ms` (porta 4221) + `email-automation-ms` (porta 4004) + OpenDKIM milter Postfix Helsinki + `agilehub-workflow-engine` (porta 4230, blocco AWE `AC30_MarketingTenantProvision`)
|
||||||
|
**Endpoint base**: `/api/marketing/*`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Scopo
|
||||||
|
|
||||||
|
Lo standard definisce il contratto cross-suite tra prodotti consumer e modulo Marketing AgileHub centralizzato. Ogni prodotto (TRPG come pilot) consuma 28 endpoint stabili `/api/marketing/*` per fornire ai propri clienti (consulting firm) un servizio email marketing white-label multi-tenant.
|
||||||
|
|
||||||
|
## 2. Multi-tenancy
|
||||||
|
|
||||||
|
| Concetto | Identificativo | Source of truth |
|
||||||
|
|---|---|---|
|
||||||
|
| Tenant marketing | `tenant_id` (es. `tnt_agile-technology_b4f9a3141333`) | `nexus_marketing_db.tenants` |
|
||||||
|
| Consulting firm consumer | `consulting_firm_id` (FK logico) | DB del prodotto consumer (es. `trpg.consulting_firms`) |
|
||||||
|
| `firm_slug` | derivato da `slugify(consulting_firm.name)` | calcolato server-side al provisioning |
|
||||||
|
| API key per-tenant | `ah_live_*` (prod) / `ah_test_*` (sandbox) | `nexus_marketing_db.api_keys` |
|
||||||
|
|
||||||
|
Vincolo: 1:1 tra `tenant_id` e `consulting_firm_id` nel prodotto consumer (nessuna 1:N).
|
||||||
|
|
||||||
|
**Naming convention `firm_slug`** (formalizzato v1.1):
|
||||||
|
- Algoritmo: `slugify(consulting_firm.name)` — lowercase ASCII, separatore `-`, rimozione caratteri non alfanumerici, max 64 char
|
||||||
|
- Esempi:
|
||||||
|
- `"Tremolada Consulting S.r.l."` → `tremolada-consulting`
|
||||||
|
- `"Agile Technology s.r.l."` → `agile-technology`
|
||||||
|
- `"O'Brien & Co."` → `obrien-co`
|
||||||
|
- `tenant_id` auto-generato server-side: `tnt_${firm_slug}_${randomId(6)}` (esempio: `tnt_agile-technology_b4f9a3141333`)
|
||||||
|
- Sottodominio derivato: `{firm_slug}.agile.software` (es. `agile-technology.agile.software`)
|
||||||
|
- Reply-to default: `noreply@{firm_slug}.agile.software`
|
||||||
|
- Il consumer NON costruisce mai il `firm_slug` autonomamente: lo riceve in risposta a `POST /admin/tenants` e lo persiste come dato derivato
|
||||||
|
|
||||||
|
## 3. Autenticazione
|
||||||
|
|
||||||
|
- Header obbligatorio: `X-AgileHub-Key: <api_key_plaintext>`
|
||||||
|
- Header obbligatorio: `X-AgileHub-API-Version: 1.0`
|
||||||
|
- Hash SHA-256 della key in DB (`api_keys.key_hash`); plaintext mai persistito leggibile (cifrato AES-256-GCM in `api_keys.key_encrypted`)
|
||||||
|
- Provisioning admin via endpoint interno `/api/marketing/admin/tenants/:id/api-keys` (header `X-Internal-Key` solo Esperto Agile/installer)
|
||||||
|
|
||||||
|
## 4. API versioning policy
|
||||||
|
|
||||||
|
- Header request: `X-AgileHub-API-Version: 1.0` (obbligatorio)
|
||||||
|
- Header response: `X-AgileHub-API-Version: 1.0` (echo)
|
||||||
|
- Header response: `X-Request-Id: req_<uuid>` (cross-system tracing)
|
||||||
|
- Deprecation policy: 6 mesi notice (`X-Deprecation-Date`) + 6 mesi sunset (`X-Sunset-Date`) prima rimozione versione precedente
|
||||||
|
- Canary opt-in: `X-API-Canary: true` su feature flag tenant
|
||||||
|
|
||||||
|
## 5. Rate limiting
|
||||||
|
|
||||||
|
| Endpoint family | Default | Override |
|
||||||
|
|---|---|---|
|
||||||
|
| Read endpoints | 60 req/min | – |
|
||||||
|
| `/contacts/import` | 10 req/min | `RATE_LIMIT_IMPORT` env |
|
||||||
|
| `/segments/preview` | 20 req/min | `RATE_LIMIT_PREVIEW` env |
|
||||||
|
| Tracking pixel/click | nessuno (pubblico) | – |
|
||||||
|
|
||||||
|
Header response: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.
|
||||||
|
Risposta 429 con `retry_after_sec`.
|
||||||
|
|
||||||
|
## 6. Error envelope
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "STRING_CODE",
|
||||||
|
"message": "Human readable",
|
||||||
|
"details": { ... opzionale ... }
|
||||||
|
},
|
||||||
|
"request_id": "req_<uuid>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Codici stabili documentati: `MISSING_API_KEY`, `INVALID_API_KEY`, `API_KEY_REVOKED`, `API_KEY_EXPIRED`, `TENANT_NOT_FOUND`, `TENANT_DELETED`, `TENANT_SUSPENDED`, `UNSUPPORTED_API_VERSION`, `RATE_LIMITED`, `DSL_INVALID`, `DSL_TOO_DEEP`, `DSL_FIELD_NOT_ALLOWED`, `DSL_OP_NOT_ALLOWED`, `CONTACT_SUPPRESSED`, `CONTACT_NOT_FOUND`, `INVALID_EMAIL`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_LOCKED`, `CAMPAIGN_FINAL`, `CAMPAIGN_NOT_SCHEDULABLE`, `NO_RECIPIENTS`, `TEMPLATE_NOT_FOUND`, `TEMPLATE_INVALID`, `TEMPLATE_REQUIRED`, `EXPORT_JOB_NOT_FOUND`, `IMPORT_JOB_NOT_FOUND`, `INTERNAL_ERROR`.
|
||||||
|
|
||||||
|
## 7. SLA
|
||||||
|
|
||||||
|
| Indicatore | Target | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| Uptime | 99.5% rolling 30gg | Allineato altri MS suite |
|
||||||
|
| p95 GET | <500ms | Cache Redis 30s su preview |
|
||||||
|
| p95 POST | <2000ms | DB persist + audit + idempotency |
|
||||||
|
| `/segments/preview` 50k contatti | <2s | Cache 30s + indici DB |
|
||||||
|
| RTO | 30 min | mysqldump nightly + restore documentato |
|
||||||
|
| RPO | 1h | Replication MySQL slave (futuro) |
|
||||||
|
|
||||||
|
## 8. GDPR compliance
|
||||||
|
|
||||||
|
- Double opt-in obbligatorio (POST `/contacts` con `consent_pre_confirmed=false` invia email confirm)
|
||||||
|
- Footer unsubscribe RFC 8058 auto in tutti i template (one-click `List-Unsubscribe` + `List-Unsubscribe-Post`)
|
||||||
|
- Suppression list permanente per-tenant (`unsubscribe`, `complaint`, `hard_bounce`, `gdpr_request`)
|
||||||
|
- Hard delete contact (`POST /contacts/:id/hard-delete`) ritorna `audit_trail_id`
|
||||||
|
- Export ZIP portabilità (`POST /exports`) — contacts.csv + campaigns.json + suppression.csv
|
||||||
|
- Offboarding tenant: 90gg grace → hard delete (zero retention post-offboard)
|
||||||
|
- Retention metrics_events: 90gg rolling, cron pulizia notturna
|
||||||
|
|
||||||
|
## 9. Branding tenant
|
||||||
|
|
||||||
|
- Sottodominio `{firm-slug}.agile.software` per envelope From
|
||||||
|
- 4 placeholder firm: `firm.name`, `firm.logo_url`, `firm.primary_color`, `firm.accent_color`
|
||||||
|
- 3 personalization tag contact: `contact.first_name`, `contact.last_name`, `contact.email`
|
||||||
|
- Reply-to configurabile per tenant
|
||||||
|
- DKIM selector dedicato per tenant (deliverability isolation)
|
||||||
|
|
||||||
|
## 10. 28 endpoint
|
||||||
|
|
||||||
|
Vedi `/var/www/agile-services/nexus-marketing-ms/src/routes/*` o ticket `TICKET_AGILEHUB_MARKETING_API.md` per spec completa. Riassunto:
|
||||||
|
|
||||||
|
- Sprint 2 (5 read-only): `/tenants/me`, `/contacts` GET, `/campaigns` GET/{id}, `/templates` GET, `/metrics/overview`
|
||||||
|
- Sprint 3 (12 write): contacts POST, segments POST/GET/preview, campaigns POST/PATCH/schedule/cancel, templates render, test-send, metrics per-campaign
|
||||||
|
- Sprint 4 (10 GDPR): contacts PATCH/DELETE/hard-delete/import, exports POST/{id}, quota, admin tenants suspend/resume/delete
|
||||||
|
|
||||||
|
## 11. Sandbox
|
||||||
|
|
||||||
|
- URL primary: `https://agilehub-staging.agile.software/api/marketing` (LIVE 2026-04-25)
|
||||||
|
- URL secondary (legacy): `https://staging.agilehub.agile.software/api/marketing`
|
||||||
|
- Tenant test corrente: `tnt_agile-technology_b4f9a3141333` (`plan='enterprise_test'`, `quota_default=5000`)
|
||||||
|
- Tenant precedente: `tnt_test_tremolada_001` (suspended 2026-04-25 sera, swap-out per testing su entità Agile Technology s.r.l.)
|
||||||
|
- Key dedicata: `ah_test_*` (mai mescolare con `ah_live_*`)
|
||||||
|
- DB: `nexus_marketing_db` (clone notturno cron)
|
||||||
|
- Subdomain tenant: `{firm_slug}.agile.software` (es. `agile-technology.agile.software`) — zone Cloudflare-managed, automazione full
|
||||||
|
- DNS deliverability sandbox: SPF + DMARC + **DKIM** tutti `active` (LIVE 2026-04-26 mattina)
|
||||||
|
- **DKIM signing**: OpenDKIM milter su Postfix Helsinki, selector `agile2026`, RSA 2048, rotation annuale Q1
|
||||||
|
|
||||||
|
## 12. Deroghe
|
||||||
|
|
||||||
|
I prodotti consumer non possono opporsi unilateralmente. Per esenzioni: aprire ticket AgileHub con tag `standard-exception` + motivazione tecnica + termine. MARKETER + REGENT decidono entro 5gg lavorativi.
|
||||||
|
|
||||||
|
## 13. Distribuzione
|
||||||
|
|
||||||
|
| Prodotto | docs_file | claude_md_section | claude_memory | adoption_status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| trpg | ✅ TODO | ✅ TODO | ✅ TODO | pending → acknowledged after deploy |
|
||||||
|
| sustainai | TBD M5 | – | – | pending |
|
||||||
|
| nis2 | TBD M5 | – | – | pending |
|
||||||
|
| altri 8 | TBD post-Tremolada | – | – | pending |
|
||||||
|
|
||||||
|
## 14. Riferimenti
|
||||||
|
|
||||||
|
- Spec produttore TRPG: `/var/www/trpg-agile/docs/PROMPT_MARKETING_FEATURE.md` v1.3
|
||||||
|
- Spec contratto: `/var/www/trpg-agile/docs/TICKET_AGILEHUB_MARKETING_API.md`
|
||||||
|
- Doc agente MARKETER: `docs/AGENT_MARKETER.md`
|
||||||
|
- Reply formale TRPG: `docs/REPLY_TO_TRPG_MARKETING_REQUEST_DRAFT.md`
|
||||||
|
- Audit gap: `docs/HANDOVER_FROM_TRPG_MARKETING_FEATURE_REQUEST.md`
|
||||||
|
|
||||||
|
## 15. Changelog
|
||||||
|
|
||||||
|
### v1.4 (2026-04-26 mattina, AC30 blocco AWE pronto in branch — MAESTRO impl + 11/11 test)
|
||||||
|
- **Nuovo blocco AWE `AC30_MarketingTenantProvision`** implementato + testato (619 righe blocco + 397 righe test, file untracked nel branch `feature/awe-m1-m2-foundation`)
|
||||||
|
- **Orchestrazione atomica** end-to-end provisioning tenant marketing in 1 nodo workflow: tenant create + 3 DNS Cloudflare (A + SPF + DMARC) + DKIM SSH exec `dkim-provision.js` + DPA opzionale + API key generation
|
||||||
|
- **Compensation pattern**: hook `compensate(ctx, result)` engine-level + rollback inline su partial-state failure (cancella DNS records, revoca DKIM via `--revoke`, soft-delete tenant 90gg grace)
|
||||||
|
- **Idempotency H7**: `sha256(runId + nodeId + {firmSlug, firmLegalName})` TTL 7gg
|
||||||
|
- **Dry-run mode** completo (mock conforme `outputSchema`, no side-effect)
|
||||||
|
- **Vault integration**: `tier1__nexus-hub-ms__cloudflare/api_token` per CF token, `INTERNAL_SERVICE_KEY` env per X-Internal-Key marketing-ms admin endpoints
|
||||||
|
- **SSH credential**: pattern `loadAndDecrypt(sshCredentialId)` riusato da AC23
|
||||||
|
- **Auto-loaded** dal catalog registry (`src/blocks/index.js`) come 13° blocco `action`
|
||||||
|
- **Test verdi**: 11/11 (6 scenari §9 handover MAESTRO + 3 schema/shape + 2 dryRun) — happy path + rollback DNS + rollback DKIM + skipDkim + firm_slug duplicato + idempotency
|
||||||
|
- **Trigger originale Q3 2026** (≥3 tenant prod), implementazione **anticipata** disponibile on-demand
|
||||||
|
- **Decisione GO/NO-GO deploy**: pending — richiede `pm2 reload agilehub-workflow-engine` + bump health.js block count + smoke E2E contro marketing-ms reale
|
||||||
|
- **Spec autoritativa**: [`HANDOVER_AC30_MARKETING_TENANT_PROVISION_FOR_MAESTRO.md`](HANDOVER_AC30_MARKETING_TENANT_PROVISION_FOR_MAESTRO.md)
|
||||||
|
- **Coordinamento agenti** (al deploy): MAESTRO (impl) + TITAN (SSH exec coord) + MARKETER (acceptance + bump v1.5 con esempio workflow JSON pubblicato) + PRISMA (form auto-render verify) + VIGILE (security inventory blocco side-effect)
|
||||||
|
|
||||||
|
### v1.3 (2026-04-26 mattina, DKIM per-tenant LIVE — TITAN Phase 1-4)
|
||||||
|
- **DKIM signing OPERATIVO**: OpenDKIM 2.11.0 installato su Postfix Helsinki, milter `inet:localhost:8891`, `milter_default_action=accept` (fail-open per zero downtime)
|
||||||
|
- **Schema scope chiave**: 1 keypair RSA 2048 per-tenant, selector `agile2026._domainkey.{firm_slug}.agile.software`. Pattern conferma standard §16 (deliverability isolation per-tenant).
|
||||||
|
- **Tenant Agile Technology**: keypair generata + DNS TXT pubblicato Cloudflare + KeyTable + SigningTable + DB UPDATE → `dkim_status='active'`
|
||||||
|
- **Automation script**: `scripts/dkim-provision.js` self-contained Node.js (no npm install, mysql CLI + https native + execSync). Idempotente. Eseguito on-host Hetzner.
|
||||||
|
- **§16 ggiornato**: `Strategia DKIM sandbox vs prod` rimossa "skip sandbox" (DKIM ora attivo anche in sandbox per testing realistico). Mantengono `dkim_status='not_required'` come stato simbolico per tenant esplicitamente esentati.
|
||||||
|
- **Rotation policy**: annuale Q1 (Q1 2027 prossimo turno), dual-key 30gg overlap durante transizione, coordinata con VIGILE Pillar 1
|
||||||
|
- **Backward compat**: 99.994% del traffico Postfix Helsinki (cron, www-data, noreply@agile.software) NON matcha SigningTable → continua identica a oggi (15.466 email/7gg invariate, verificato empiricamente)
|
||||||
|
|
||||||
|
### v1.2 (2026-04-25 sera, post-allineamento Cristiano TRPG — stesso giorno di v1.1)
|
||||||
|
- **Convention dominio cambiata**: `*.agilehub.it` → `*.agile.software` (correzione architetturale: Agile ha un solo dominio commerciale `agile.software`, già zone Cloudflare-managed per altri sottodomini come `trpg.agile.software`, `agilehub.agile.software`)
|
||||||
|
- **Schema `tenant_branding.dkim_status`**: ALTER additive enum, aggiunto valore `not_required` per sandbox/non-prod (oltre a `pending`/`active`/`failed`)
|
||||||
|
- **Tenant `tnt_agile-technology_b4f9a3141333`**: UPDATE branding subdomain → `agile-technology.agile.software`, reply_to → `noreply@agile-technology.agile.software`, dkim_status=not_required, spf_status=active, dmarc_status=active
|
||||||
|
- **§16 riscritto**: sostituito vecchio scenario register.it con scenario reale Cloudflare-managed
|
||||||
|
- **§11 Sandbox**: aggiornato per nuovo subdomain pattern + DNS deliverability LIVE
|
||||||
|
- **DKIM strategia**: skipped per sandbox, **mandatory per produzione** (go-live Tremolada 23/6 — vedi §16)
|
||||||
|
|
||||||
|
### v1.1 (2026-04-25 sera, superseded by v1.2 stesso giorno)
|
||||||
|
- **§2 Multi-tenancy**: formalizzato `firm_slug = slugify(consulting_firm.name)` come algoritmo derivato server-side. Aggiunti 3 esempi concreti, regola `tenant_id = tnt_${firm_slug}_${randomId(6)}`, derivazione automatica sottodominio + reply-to.
|
||||||
|
- **§11 Sandbox**: aggiornato tenant corrente a `tnt_agile-technology_b4f9a3141333` (sostituisce `tnt_test_tremolada_001` suspended)
|
||||||
|
- v1.1 documentava convention `*.agilehub.it` (corretta in v1.2 stesso giorno)
|
||||||
|
|
||||||
|
## 16. Gestione zone DNS `*.agile.software` + deliverability
|
||||||
|
|
||||||
|
**Provider DNS authoritative**: Cloudflare (zone id `e3d677355677a6397d2caa77264cbfa2`).
|
||||||
|
**MX inbound**: separato (Microsoft 365 su zone `agilehub.it`); modulo Marketing fa SOLO outbound, no impatto MX.
|
||||||
|
**Automazione**: full via Cloudflare API (token in vault `tier1__nexus-hub-ms__cloudflare/api_token`).
|
||||||
|
|
||||||
|
**Pattern record per tenant** (provisioning automatico con MARKETER+TITAN):
|
||||||
|
| Record | Esempio Agile Technology | Scope |
|
||||||
|
|---|---|---|
|
||||||
|
| `A` | `agile-technology.agile.software → 135.181.149.254` | Helsinki Postfix outbound |
|
||||||
|
| `TXT` SPF | `v=spf1 ip4:135.181.149.254 -all` | autorizzazione mittente |
|
||||||
|
| `TXT` DMARC | `_dmarc.agile-technology.agile.software TXT v=DMARC1; p=none; rua=mailto:dmarc-reports@agile.software; pct=100` | report-only modalità monitoring |
|
||||||
|
| `TXT` DKIM | `agile2026._domainkey.agile-technology.agile.software TXT v=DKIM1; k=rsa; p=<pubkey>` | **solo PROD**, skip sandbox |
|
||||||
|
|
||||||
|
**Strategia DKIM sandbox vs prod (v1.3 LIVE 2026-04-26)**:
|
||||||
|
- **DKIM mandatory per tutti i tenant produttivi** (no skip). DMARC inizia `p=none` (monitoring) e bumppa `p=quarantine` poi `p=reject` quando deliverability stabile post-3 settimane di metriche pulite.
|
||||||
|
- **`dkim_status='not_required'`**: stato simbolico per tenant esplicitamente esentati (es. tenant disabilitati o test temporanei senza traffico real-world). Widget consumer lo mappa come "n/a".
|
||||||
|
- **Sandbox attiva DKIM by default** dal v1.3: `agile-technology.agile.software` ha DKIM `active` per testing realistico pre-prod.
|
||||||
|
|
||||||
|
**Effort provisioning per nuovo tenant prod (con script automation v1.3)**:
|
||||||
|
1. POST `/admin/tenants` (auto-genera firm_slug + subdomain) — 1s
|
||||||
|
2. POST 3 record Cloudflare base (A + SPF + DMARC) — manuale via curl o futuro workflow AWE — 10s
|
||||||
|
3. **`node scripts/dkim-provision.js --tenant-id <id> --firm-slug <slug>`** ON HOST HETZNER (root):
|
||||||
|
- genera keypair RSA 2048 (idempotente)
|
||||||
|
- append KeyTable + SigningTable
|
||||||
|
- publish DNS DKIM TXT su Cloudflare (idempotente, PATCH se esiste)
|
||||||
|
- UPDATE `tenant_branding.dkim_*` + `dkim_status='active'`
|
||||||
|
- `systemctl reload opendkim` hot
|
||||||
|
- **~3-5 secondi end-to-end**
|
||||||
|
|
||||||
|
**Totale**: ~15s tenant pronto. Future workflow AWE blocco `AC30_MarketingTenantProvision` Q3 2026 può chiamare script via webhook host.
|
||||||
|
|
||||||
|
**Rotation policy DKIM** (annuale Q1, coord VIGILE):
|
||||||
|
1. Q1 ogni anno: VIGILE notifica scadenza
|
||||||
|
2. TITAN: nuovo selector (es. `agile2027` per Q1 2027), genera keypair nuovo
|
||||||
|
3. Pubblica DNS DKIM nuovo (entrambi vecchio+nuovo coesistono per 30gg overlap)
|
||||||
|
4. Aggiorna `KeyTable` + `SigningTable` per usare nuovo selector
|
||||||
|
5. `systemctl reload opendkim` → email firmate con nuova chiave
|
||||||
|
6. T+30gg: revoca vecchio selector (DELETE DNS vecchio + cleanup KeyTable/SigningTable)
|
||||||
|
7. Audit log TITAN+VIGILE entry coordinated
|
||||||
|
|
||||||
|
**Comandi rapidi rotation** (futuro Phase 4 enhancement):
|
||||||
|
```bash
|
||||||
|
# T0: nuova chiave
|
||||||
|
node scripts/dkim-provision.js --tenant-id <id> --firm-slug <slug> --selector agile2027
|
||||||
|
|
||||||
|
# T+30gg: revoca vecchia
|
||||||
|
node scripts/dkim-provision.js --revoke --tenant-id <id> --firm-slug <slug> --selector agile2026
|
||||||
|
```
|
||||||
160
docs/STANDARD_MULTITENANT_ARCHITECTURE.md
Normal file
160
docs/STANDARD_MULTITENANT_ARCHITECTURE.md
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# STANDARD AgileHub: multitenant-architecture v1.0
|
||||||
|
|
||||||
|
> **Codename progetto**: NAVIGAI (Navigare nell'AI)
|
||||||
|
> **Status**: `proposed`
|
||||||
|
> **Applies to**: `*` (tutta la suite — 11 prodotti)
|
||||||
|
> **Owner**: REGENT (coordinamento) + TITAN (backend) + VIGILE (audit) + Agile AI (KB master) + PRISMA (UI master/tenant)
|
||||||
|
> **Date**: 2026-05-17
|
||||||
|
> **Public facing**: AgileHub (NAVIGAI è il codename interno, invisibile al cliente)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Scopo
|
||||||
|
|
||||||
|
Definire l'architettura multitenant esplicita di AgileHub con un livello master condiviso, tenant client isolati, catalogo cross-tenant condivisibile, billing per-tenant e governance centralizzata.
|
||||||
|
|
||||||
|
## 2. Tassonomia tenant
|
||||||
|
|
||||||
|
| Tipo | Esempio | id | is_master | tier | parent_tenant_id |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| **Master** | AgileHub | 1 | TRUE | `master` | NULL |
|
||||||
|
| Client enterprise | Work Group s.r.l. | N | FALSE | `enterprise` | 1 |
|
||||||
|
| Client professional | Studio Tremolada | N | FALSE | `professional` | 1 |
|
||||||
|
| Client trial | sandbox | N | FALSE | `trial` | 1 |
|
||||||
|
| Sandbox interno | Agile Technology test | N | FALSE | `trial` | 1 |
|
||||||
|
|
||||||
|
## 3. Visibility ENUM cross-tabella
|
||||||
|
|
||||||
|
Ogni record sensibile (KB articles, personas, workflow templates, routing rules, RAG repositories) ha colonna `visibility ENUM('global','shared','tenant','team','private')`.
|
||||||
|
|
||||||
|
| Valore | Significato | Lettura da |
|
||||||
|
|---|---|---|
|
||||||
|
| `global` | Master AgileHub catalog, riusabile read-only da tutti i tenant | tutti |
|
||||||
|
| `shared` | Visibile a tenant + i suoi sotto-tenant | tenant + figli |
|
||||||
|
| `tenant` | Solo tenant proprietario | tenant |
|
||||||
|
| `team` | Team specifico dentro un tenant | team members |
|
||||||
|
| `private` | Singolo utente | owner_user_id |
|
||||||
|
|
||||||
|
## 4. Opt-out granulare
|
||||||
|
|
||||||
|
Tabella `nexus_tenant_db.tenant_global_exclusions(tenant_id, resource_type, resource_id, exclusion_reason)` permette al client di dichiarare "non voglio usare quell'articolo KB master/quella persona master/quella regola routing master".
|
||||||
|
|
||||||
|
## 5. JWT additivi (no breaking)
|
||||||
|
|
||||||
|
Claims aggiunti (additive, retro-compat):
|
||||||
|
- `tenant_id BIGINT`
|
||||||
|
- `tenant_slug VARCHAR(64)`
|
||||||
|
- `is_master TINYINT(1)`
|
||||||
|
- `permitted_tenant_ids INT[]` (per is_master users che possono switchare contesto)
|
||||||
|
|
||||||
|
Vecchi JWT senza nuovi claim restano validi → tenant_id desunto via fallback `tenants.email → user → tenant_membership`.
|
||||||
|
|
||||||
|
## 6. Shared library
|
||||||
|
|
||||||
|
Pacchetto npm interno `@agile/tenant-auth` (Node) + `agile_tenant_auth` (Python) fornisce:
|
||||||
|
- `extractTenantContext(req)` → `{ tenant_id, tenant_slug, is_master, user_id }`
|
||||||
|
- `requireSameTenant(req, resourceTenantId)` → throws 403 se mismatch
|
||||||
|
- `requireMaster(req)` → throws 403 se !is_master
|
||||||
|
- `withTenantFilter(query)` → injects `WHERE (tenant_id = X OR visibility = 'global')`
|
||||||
|
|
||||||
|
## 7. Vault namespace tenant-aware
|
||||||
|
|
||||||
|
Pattern: `tier1__<ms>__<tenant_slug>__<feature>`
|
||||||
|
|
||||||
|
Esempi:
|
||||||
|
- `tier1__nexus-marketing-ms__work_group_001__mailgun_api_key`
|
||||||
|
- `tier1__nexus-presenter-ms__agilehub_master__tavus_api_key`
|
||||||
|
|
||||||
|
Compatibility: namespace senza `<tenant_slug>` restano validi per master.
|
||||||
|
|
||||||
|
## 8. Audit log HMAC chain
|
||||||
|
|
||||||
|
JSONL append-only in `docs/multitenant-audit/YYYY-MM-WNN.jsonl` con catena HMAC-SHA256 (ogni entry contiene SHA del precedente). Genesis entry firmata da TITAN al kickoff SG-0.
|
||||||
|
|
||||||
|
## 9. Distributed tracing
|
||||||
|
|
||||||
|
Header `X-Trace-Id` propagato cross-MS in tutti i call HTTP/Redis Streams. Generato in API gateway Apache se assente. Pino structured log + Loki backend.
|
||||||
|
|
||||||
|
## 10. Status page tenant-aware
|
||||||
|
|
||||||
|
`/status` Next.js mostra SLA + uptime + latency p99 **per-tenant** (con auth). SLA differenziato per tier:
|
||||||
|
- master: 99.99%
|
||||||
|
- enterprise: 99.95%
|
||||||
|
- professional: 99.9%
|
||||||
|
- trial: 99%
|
||||||
|
|
||||||
|
## 11. Canary deploy
|
||||||
|
|
||||||
|
Apache `mod_proxy_balancer` rolling 5% → 50% → 100% con auto-rollback se error rate > 2% per 5 min.
|
||||||
|
|
||||||
|
## 12. Billing per-tenant
|
||||||
|
|
||||||
|
Tabella `nexus_hub.hub_cost_events(tenant_id, ms, event_type, cost_eur_micros, ts)` con 8 hook nei MS:
|
||||||
|
- nexus-ai-ms (tokens LLM Anthropic + Voyage embedding)
|
||||||
|
- nexus-marketing-ms (email sends via Mailgun)
|
||||||
|
- nexus-call-ms (Twilio minutes)
|
||||||
|
- nexus-presenter-ms (Tavus session-minutes + LiveKit)
|
||||||
|
- nexus-voice-ms (Deepgram STT + ElevenLabs TTS)
|
||||||
|
- nexus-rag-ms (Voyage embed/rerank + storage GB)
|
||||||
|
- nexus-hub-ms (server time)
|
||||||
|
- agilehub-workflow-engine (Haiku suggester tokens)
|
||||||
|
|
||||||
|
Export mensile CSV/PDF (showback) per cliente.
|
||||||
|
|
||||||
|
## 13. Compliance
|
||||||
|
|
||||||
|
- **GDPR Art.32**: encryption at rest+transit, access controls, audit trail, business continuity, periodic testing — tutti documentati in `docs/COMPLIANCE_GDPR_ART32.md`
|
||||||
|
- **ISO A.18.1.5** readiness: gap analysis NON certificazione formale
|
||||||
|
- **Retention fiscale IT**: 10 anni per `hub_cost_events` (NON 7)
|
||||||
|
- **GDPR Art.17 erasure**: cascade già implementato in nexus-rag-ms (estendere ai 13 MS)
|
||||||
|
|
||||||
|
## 14. Pen test esterno
|
||||||
|
|
||||||
|
STRIDE 13×6 matrix (13 MS × 6 categorie) eseguita da vendor EU certificato durante SG-5. Budget €15K una tantum. Vendor RFP da VIGILE.
|
||||||
|
|
||||||
|
## 15. Roadmap distribution
|
||||||
|
|
||||||
|
| Wave | Prodotti | Trigger |
|
||||||
|
|---|---|---|
|
||||||
|
| **Reference** | AGILEHUB | SG-0 in progress |
|
||||||
|
| **Wave 1 (P1)** | TRPG, SUSTAINAI, NIS2, DFM | SG-4 GREEN |
|
||||||
|
| **Wave 2 (P2)** | TAXAI, LG231, MKTG, ALLRISK, WMS, MADEBYCLOUD, CERTISOURCE | post smoke Wave 1 |
|
||||||
|
|
||||||
|
Distribution via INSTALLATORE pattern: docs_file scp + claude_md append + claude memory write nei 11 container DevEnv prodotto + `hub_standards_adoption` row per ogni prodotto (status `pending → acknowledged → implemented`).
|
||||||
|
|
||||||
|
## 16. NESSUN out-of-scope (v1.0)
|
||||||
|
|
||||||
|
Documentato esplicitamente FUORI da v1.0:
|
||||||
|
- Multi-region failover (Hetzner Helsinki + EU secondary)
|
||||||
|
- Self-service tenant signup pubblico con CC payment
|
||||||
|
- Multi-currency (solo EUR)
|
||||||
|
- SAML/OIDC federation
|
||||||
|
- Tenant white-label dominio custom (solo subdomain `{slug}.agilehub.it`)
|
||||||
|
- Audit log immutabile blockchain
|
||||||
|
- ISO 27001 / SOC 2 certification formale
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adoption tracker (status iniziale)
|
||||||
|
|
||||||
|
| Prodotto | docs_file | claude_md | claude_memory | adoption_status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| AGILEHUB | ✓ pending SG-0 | pending | pending | `proposed` |
|
||||||
|
| TRPG | pending | pending | pending | `pending` |
|
||||||
|
| SUSTAINAI | pending | pending | pending | `pending` |
|
||||||
|
| NIS2 | pending | pending | pending | `pending` |
|
||||||
|
| TAXAI | pending | pending | pending | `pending` |
|
||||||
|
| LG231 | pending | pending | pending | `pending` |
|
||||||
|
| DFM | pending | pending | pending | `pending` |
|
||||||
|
| MKTG | pending | pending | pending | `pending` |
|
||||||
|
| ALLRISK | pending | pending | pending | `pending` |
|
||||||
|
| WMS | pending | pending | pending | `pending` |
|
||||||
|
| MADEBYCLOUD | pending | pending | pending | `pending` |
|
||||||
|
| CERTISOURCE | pending | pending | pending | `pending` |
|
||||||
|
|
||||||
|
## Riferimenti
|
||||||
|
|
||||||
|
- [NAVIGAI_EXECUTIVE_BRIEF.md](NAVIGAI_EXECUTIVE_BRIEF.md)
|
||||||
|
- [NAVIGAI_ARCHITETTURA_TECNICA.md](NAVIGAI_ARCHITETTURA_TECNICA.md)
|
||||||
|
- [NAVIGAI_ROADMAP_OPERATIVA.md](NAVIGAI_ROADMAP_OPERATIVA.md)
|
||||||
|
- [PLAN_MULTITENANT_MASTER_REFACTOR_V11.md](PLAN_MULTITENANT_MASTER_REFACTOR_V11.md) (920 righe production-ready)
|
||||||
1432
docs/standards/STANDARD_PERSONA_CONVERSATIONAL_RULES.md
Normal file
1432
docs/standards/STANDARD_PERSONA_CONVERSATIONAL_RULES.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user