This commit is contained in:
valenti
2026-09-08 07:21:34 -04:00
commit a4b224da8e
169 changed files with 27483 additions and 0 deletions
+654
View File
@@ -0,0 +1,654 @@
# ASF — Guida Primi Passi: Creare il tuo primo Progetto
> **ASF = Azurian Software Factory** · *SaaS Platform multi-tenant AI-native*
>
> **Motto**: *"From Requirement to Production — everything is contextualized, traceable and AI-accessible."*
>
> **Principio centrale**: ***Everything is an Artifact*** — requisiti, task, PR, commit, build, test, deploy, incidenti, analisi AI, segnalazioni di sicurezza: tutto è un Artifact ASF collegato da Relazioni semantiche. Questo permette agli agenti AI di capire **PERCHÉ** un pezzo di codice esiste (non solo **cosa** fa), tracciandone l'origine, i test, il ciclo di rilascio e gli effetti in produzione.
Questa guida ti porta da **zero a primo grafo di traceability end-to-end funzionante** in 8 piccoli step, usando direttamente le API REST di ASF Core e verificando ogni risultato sul Frontend.
---
## 🧱 Stack Tecnologico (da Specifiche ASF)
ASF è disegnato come "pragmatic monolith → services": **nessun microservizio prematuro** per apparenza architetturale.
| Layer | Tecnologie | Stato MVP |
|---|---|---|
| 🎨 **Frontend** | Next.js 14 · React · TypeScript · Tailwind · shadcn/ui · TanStack Query v5 · React Flow · SSE/WebSocket realtime | ✅ attivo su :3000 |
| ⚙️ **Backend Core (asf-core)** | Python 3.12 · FastAPI · Pydantic v2 · async/await | ✅ attivo su :8000 · OpenAPI `/docs` |
| 🗄️ **Persistence** | **MongoDB rs0** (Motor async) · 2 nodi replica set | ✅ PRIMARY 172.19.40.102 · SEED 172.19.40.103 |
| 📢 **Messaging Backbone** | **RabbitMQ** (aio_pika connect_robust) · vhost `asf` · **2 canali logici**: (1) **Async Jobs** su code DIRECT (2) **Domain Events** (requirement.created / deployment.completed) | ✅ 7 code DIRECT iscritte dal worker |
| 🤖 **AI Layer (asf-ai)** | **ASF AI Gateway = ASTRAZIONE TOTALE** · Zero accoppiamento diretto a provider LLM · Supporta NVIDIA DGX Spark · OpenAI-compatibile · Custom provider. Futuri: model routing · tenant AI policies · usage metering · priority queues · cost tracking | ✅ astrazione + MockProvider (7/7 tests PASS) |
| 📚 **Context Service (asf-context)** | BFS relazioni + (futuro) semantic retrieval vettoriale · Design preparato per **passare in futuro a graph DB senza re-write totale** (Mongo è source of truth MVP ma le relazioni sono disaccoppiate) | ✅ ContextEngine BFS depth=2 · 4 tests PASS |
| 🧰 **Worker (asf-worker)** | Job consumer orizzontalmente scalabile · NO stato critico in memoria · Echo handler MVP | ✅ loop while-true respawn 5 jobs echo completati |
| 🔌 **Osservabilità** | Structured logging JSON · correlation_id + tenant_id + project_id + job_id propagati in tutta distribuzione · OpenTelemetry-ready | ✅ logging strutturato + correlation middleware |
| ⏭️ **(Futuri)** | Redis caching / pubsub · MinIO/S3 object storage · Git/CI/Docs/Observability connectors · MCP · Webhooks | ⏭️ boundary presenti, non MVP attivo |
---
## 📋 0. Prerequisiti
ASF è già avviato in locale, senza Docker: verificalo con `make status`.
| Comando | Descrizione |
|---|---|
| `make start` | Avvia ASF Core (FastAPI :8000) + ASF Web (Next.js :3000) + Worker |
| `make status` | Controlla PIDs, porte, health check, ultimi log |
| `make stop` | Ferma tutto in modo pulito (SIGTERM → SIGKILL fallback) |
| `make lint` | pytest 5 packages + tsc strict no-emit |
**Health check (Core)**:
```bash
curl -s http://localhost:8000/api/v1/health | python -m json.tool
```
**Frontend**: apri `http://localhost:3000` nel browser.
---
## 🧭 Modello ASF: Multi-Tenant + Everything is an Artifact
```
┌──────────────────────────────────────────────────────────────────┐
│ Organization (Tenant) │ ← SSO · API keys · quotas · subscriptions · billing (§8)
│ └─ Team ──────────────────────────────────────────────────────┐ │ ← ⏭️ Futuro: RBAC · membership · permissioni per team
│ └─ Project ─────────────────────────────────────────────┐ │ │ ← Reale già oggi. Ogni Artifact appartiene a 1 Project
│ └─ Artifact (17 tipi) ←── Relationship ─→ Artifact │ │ │
└──────────────────────────────────────────────────────────────────┘
```
1. **Organization = Tenant** · es. `azurian-labs` (il tuo team/cliente, **isolamento dati perimetrale**).
- Futuro: SSO (OIDC / SAML) · API keys scopes · audit retention · subscription plan · metering · billing boundaries (non MVP, ma già boundary nel codice).
2. **Team** (⏭️ layer futuro incluso per compatibilità futura) · gruppi di utenti dentro Org con ruoli e permissioni RBAC (Owner / Admin / Editor / Viewer). MVP oggi salta il layer Team per semplicità ma lo include nel modello per non doverlo aggiungere dopo con migration.
3. **Project** · dentro una Org (e opzionalmente Team), es. `ASF` (il prodotto/servizio). **Ogni Artifact appartiene SEMPRE a un Project** (campo `project_id` obbligatorio nel `ArtifactCreate`).
4. **Artifact** (17 tipi, enum `ArtifactType` — unica sorgente di verità condivisa package `asf-domain`):
`requirement, epic, story, task, architecture_decision, document, source_file, commit, pull_request, build, test, security_finding, deployment, release, runtime_event, incident, ai_analysis`
- **Nessun modello separato per tipo** (regola §2 estensibilità). Ogni tipo è esteso col campo libero `metadata: dict[str, Any]`.
5. **Relationship** (12 predicati semantici, enum `RelationshipType` — anche questo condiviso dal package domain):
`implements, implemented_by, verifies, verified_by, generated_from, documented_by, depends_on, affects, deployed_as, caused_by, supersedes, related_to`
- Relazioni sono **directional** (source → target), quindi "il PR è generated_from dal TASK" e non viceversa.
---
## 🔑 Headers Dev-Trust (Importante!)
In locale ASF usa una modalità "dev-trust" semplificata: **per ogni richiesta aggiungi questi 2 header** per impersonare utente e tenant. **Senza questi header** le `GET /projects` e `POST /artifacts` etc. restituiranno 401 o lista vuota.
```
X-ASF-Tenant-Id: <UUID oppure slug-style deterministico>
X-ASF-User-Id: <UUID oppure stile u-<nome>>
```
Per questa guida useremo due valori coerenti (sono accettate UUID 128-bit o fallback testuali deterministici):
```
TENANT="t-demo-003"
USER="u-mario"
```
Salviamoli come **variabili ambientali** nel terminale così da non ripeterli ogni volta:
```bash
export TENANT="t-demo-003"
export USER="u-mario"
export H="http://localhost:8000/api/v1"
export H3K="http://localhost:3000/api/v1"
# Alias helper (conservali nella shell corrente):
auth() { echo "-H X-ASF-Tenant-Id:$TENANT -H X-ASF-User-Id:$USER"; }
GET() { curl -s $(auth) "$H$1"; }
POST() { curl -s -X POST $(auth) -H "Content-Type: application/json" -d @- "$H$1"; }
PATCH(){ curl -s -X PATCH $(auth) -H "Content-Type: application/json" -d @- "$H$1"; }
DEL() { curl -s -X DELETE $(auth) "$H$1"; }
```
> Nota: `$H3K` (:3000) è l'endpoint Next.js con **rewrites trasparenti** → `/api/v1/*` → `localhost:8000/api/v1/*`. La risposta è **byte-identica** al Core, utile per testare i rewrite del frontend.
---
## ✅ Step 1 — Crea la tua Organization (Tenant)
**Payload**: `OrganizationCreate { name, slug, plan?, is_active? }`
- `slug`: **min 3, max 60 caratteri**, regex `^[a-z0-9][a-z0-9\-]{1,58}[a-z0-9]$` (tutto minuscolo, trattini, inizia e finisce con alfanum).
```bash
cat <<'JSON' | POST /organizations
{
"name": "Azurian Labs Demo",
"slug": "azurian-labs-demo",
"plan": "pro",
"is_active": true
}
JSON
```
**Attesa** → HTTP 201, risposta simile a:
```json
{
"id": "0191f…", "name": "Azurian Labs Demo",
"slug": "azurian-labs-demo", "plan": "pro", "is_active": true,
"created_at": "…", "updated_at": "…"
}
```
Prendi nota dell'`id` (UUID) dell'Organization:
```bash
ORG_ID=$(GET /organizations | python -c 'import json,sys; print(json.load(sys.stdin)[0]["id"])')
echo "ORG_ID=$ORG_ID"
```
Verifica anche la rotta `/slug`:
```bash
GET /organizations/slug/azurian-labs-demo | python -m json.tool
```
---
## ✅ Step 2 — Crea il tuo primo Project
**Payload**: `ProjectCreate { name, key, description?, default_branch?, is_active? }`
- `key`: **min 2, max 20** caratteri, regex `^[A-Z][A-Z0-9]{1,19}$` (maiuscolo, prima lettera, resto lettere/numeri). Utile come prefisso leggibile per i riferimenti.
```bash
cat <<'JSON' | POST /projects
{
"name": "ASF SaaS MVP",
"key": "ASF",
"description": "Minimal Viable Product di ASF: dal requisito al deploy con tracciabilità end-to-end.",
"default_branch": "main",
"is_active": true
}
JSON
```
→ **HTTP 201 Created**. Prendi l'`id`:
```bash
PROJ_ID=$(GET /projects | python -c 'import json,sys; print(json.load(sys.stdin)[0]["id"])')
echo "PROJ_ID=$PROJ_ID"
```
**Frontend check**: apri `http://localhost:3000/` (Overview Dashboard). Vedrai il nuovo project nel conteggio `projects` (nota: Overview aggrega per tenant, non per singolo progetto).
---
## ✅ Step 3 — Crea 4 Artifact Base (REQ / TASK / PR / TEST)
Ora iniziamo il "ciclo di vita del software" che sarà il nostro primo grafo.
**Il campo `tenant_id` NON va passato** — viene iniettato server-side dagli header.
**Il campo `project_id` VA SEMPRE PASSATO** (l'Organization può avere più progetti).
Creiamo 4 artifact nello stesso project:
```bash
REQ=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{
"project_id": "$PROJ_ID",
"type": "requirement",
"external_id": "REQ-101",
"title": "Utente può autenticarsi con SSO Google",
"content": "Gli utenti finali devono poter accedere via OAuth2/OpenID Connect a Google. Accesso in SSO senza password locale. Salvare email, name, sub in anagrafica.",
"status": "open",
"source": "product-board",
"metadata": { "priority": "P0", "estimated_points": 13, "target_release": "v1.2" }
}
JSON
echo "REQ-101 = $REQ"
TASK=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{
"project_id": "$PROJ_ID",
"type": "task",
"external_id": "TASK-201",
"title": "Implementa OAuth2 Google flow + callback route",
"content": "Backend: route /auth/google /auth/callback. Salvare token in HttpOnly cookie. Rate limit login endpoint.",
"status": "in_progress",
"source": "github",
"metadata": { "assignee": "Giulia", "points": 8, "sprint": 8 }
}
JSON
echo "TASK-201 = $TASK"
PR=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{
"project_id": "$PROJ_ID",
"type": "pull_request",
"external_id": "PR#144",
"title": "feat(auth): SSO Google OAuth2 flow",
"status": "open",
"source": "github",
"content": "Implementazione del flow OAuth2 con route di callback + refresh token rotation. + tests",
"metadata": { "branch": "feat/google-sso", "author": "Giulia", "ci": "passed", "reviewers": 2 }
}
JSON
echo "PR#144 = $PR"
TEST=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{
"project_id": "$PROJ_ID",
"type": "test",
"external_id": "TEST-41",
"title": "auth/google_test.py — SSO happy path + no-refresh-token reuse",
"content": "Pytest suite: OAuth state parameter unguessable, callback 200 con code corretto, refresh invalidato dopo 1 uso.",
"status": "done",
"source": "pytest",
"metadata": { "framework": "pytest", "duration_ms": 480, "coverage_affected_files": 5 }
}
JSON
echo "TEST-41 = $TEST"
```
Verifica 4 artifact creati:
```bash
GET /artifacts?limit=100 | python -c '
import json,sys
from collections import Counter
d = json.load(sys.stdin)
print("Totale:", len(d))
for t, c in Counter(a["type"] for a in d).most_common():
print(f" - {t:18s} {c}")
'
```
Apri `http://localhost:3000/context` → nell'Artifact Picker a sinistra compaiono i 4 nuovi elementi; clicca REQ-101. Per ora Relationships e Related sono vuote: Step 4 li collega.
---
## ✅ Step 4 — Collega gli Artifact con 3 Relazioni
**Payload**: `ArtifactRelationshipCreate { relationship_type, source_artifact_id, target_artifact_id, metadata? }`
- **Regola semantica**: `source <relationship_type> target`
- `TASK implements REQUIREMENT`
- `PR generated_from TASK`
- `TEST verifies PR`
- Non serve passare `tenant_id`, `source_project_id`, `target_project_id` (validati server-side).
```bash
# 1. TASK implements REQ (il task realizza il requisito)
cat <<JSON | POST /relationships
{ "relationship_type": "implements", "source_artifact_id": "$TASK", "target_artifact_id": "$REQ" }
JSON
# 2. PR generated_from TASK (il PR nasce dal task)
cat <<JSON | POST /relationships
{ "relationship_type": "generated_from", "source_artifact_id": "$PR", "target_artifact_id": "$TASK" }
JSON
# 3. TEST verifies PR (il test prova che il PR funziona)
cat <<JSON | POST /relationships
{ "relationship_type": "verifies", "source_artifact_id": "$TEST", "target_artifact_id": "$PR" }
JSON
```
Ogni risposta HTTP 201 include un `id` e i riferimenti incrociati.
### Verifica: Context Explorer → Graph
Apri `http://localhost:3000/context`, seleziona **REQ-101**, poi tab **Graph** (4° tab):
- 🟦 **Centro** → REQ-101 (badge **SELECTED**)
- 🟣 **Anello interno HOP 1** → TASK-201 (relazione SOLIDA verde «implements»)
- ⬜ **Anello esterno HOP 2** → PR#144 (raggiunto via TASK, edge TRATTEGGIATO violetto «generated_from»)
- ⬜ **Anello esterno HOP 2** → TEST-41 (raggiunto via PR, edge TRATTEGGIATO azzurro «verifies»)
**Click sui nodi** nel grafo: il dettaglio cambia e si ricentra sul nuovo artifact (stesso comportamento del picker laterale).
MiniMap in basso-dx mostra 3 colori distinti: `indigo = 0, violet = 1, slate = 2`.
---
## ✅ Step 5 — Aggiungi Deploy + Incidente + Correlazione Sicurezza
Facciamo un "giorno dopo": il PR va in produzione ma causa un incidente, e documentiamo anche un security finding che blocca il deploy.
```bash
BUILD=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{ "project_id": "$PROJ_ID", "type": "build", "external_id": "BUILD-909",
"title": "asf-auth:v1.2.0-rc3 build nightly",
"status": "passed", "source": "buildkite",
"metadata": { "builder": "buildkite-linux-x86-64", "duration_s": 128, "cache_hit_pct": 68 } }
JSON
DEP=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{ "project_id": "$PROJ_ID", "type": "deployment", "external_id": "DEP-12",
"title": "asf-auth → prod-west-2 (canary 5%)",
"status": "deployed", "source": "argo",
"metadata": { "env": "prod", "region": "us-west-2", "canary_pct": 5, "rollback_ref": "BUILD-890" } }
JSON
SEC=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{ "project_id": "$PROJ_ID", "type": "security_finding", "external_id": "SEC-031",
"title": "Google OAuth state vulnerable to CSRF (weak random)",
"content": "Il parametro 'state' usa secrets.token_urlsafe(8) invece di 32 byte. Possibile replay cross-domain.",
"status": "open", "source": "semgrep",
"metadata": { "severity": "high", "cvss": 7.8, "detector": "ASF-SEC-203", "confidence": 0.92 } }
JSON
INC=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{ "project_id": "$PROJ_ID", "type": "incident", "external_id": "INC-5",
"title": "Login failure spike (canary west-2) — 14% errori 5xx",
"content": "Canary deploy asf-auth:v1.2.0-rc3 ha generato picco refresh_token invalid_grant. Rollback a BUILD-890 dopo 7 minuti.",
"status": "resolved", "source": "pagerduty",
"metadata": { "sev": "sev2", "mttr_min": 14, "users_affected": 820, "pager": "Mario" } }
JSON
echo "BUILD=$BUILD DEP=$DEP SEC=$SEC INC=$INC"
# Relazioni
cat <<JSON | POST /relationships # BUILD generated_from PR
{ "relationship_type": "generated_from", "source_artifact_id": "$BUILD", "target_artifact_id": "$PR" }
JSON
cat <<JSON | POST /relationships # DEP deployed_as BUILD
{ "relationship_type": "deployed_as", "source_artifact_id": "$DEP", "target_artifact_id": "$BUILD" }
JSON
cat <<JSON | POST /relationships # SEC related_to PR
{ "relationship_type": "related_to", "source_artifact_id": "$SEC", "target_artifact_id": "$PR" }
JSON
cat <<JSON | POST /relationships # INC caused_by DEP
{ "relationship_type": "caused_by", "source_artifact_id": "$INC", "target_artifact_id": "$DEP" }
JSON
```
### Vai su → `/traceability` (Graph globale)
Apri `http://localhost:3000/traceability`: dovresti vedere un grafo **8 nodi, 7 edges** circa, disposto a layer.
- **Inspector** a dx: clicca INC-5 → vedrai che è caused_by DEP-12, che deployed_as BUILD-909, e così via a ritroso fino a REQ-101.
- Auto-refresh ogni 5 secondi. MiniMap e controlli sono gli stessi del Context Graph, ma QUI vedi **tutti gli artifact del progetto**.
---
## ✅ Step 6 — Lancia il tuo primo Job (AI Interactive)
> 🎯 **Regola di design ASF §7**: lo stato **permanente** di un Job (status, result, usage, error, worker, timestamps) **vive SEMPRE su MongoDB**.
> RabbitMQ viene usato **SOLO come coda di messaggi per il passaggio lavoro**, MAI come database.
> Se RabbitMQ cadesse, i job non si perdono: restano `status=queued` o `running` su Mongo e possono essere risottomessi o ripresi. Il Worker scala orizzontalmente senza stato critico in memoria.
ASF gestisce il lavoro asincrono con **Jobs** su code RabbitMQ DIRECT. 7 code logiche DIRECT già registrate (priorità decrescente):
`asf.ai.interactive > asf.ai.background > asf.testing > asf.code_analysis > asf.devops > asf.notifications > asf.indexing`
Prova con 3 job demo (echo handler integrato nel worker):
```bash
# Job 1 - DevOps
cat <<'JSON' | POST /jobs/submit
{ "queue": "devops", "job_type": "devops", "title": "deploy staging preflight",
"payload": { "service": "asf-auth", "env": "staging", "mode": "preflight" },
"priority": 50 }
JSON
# Job 2 - Testing
cat <<'JSON' | POST /jobs/submit
{ "queue": "testing", "job_type": "testing", "title": "regression suite auth",
"payload": { "suite": "auth/google_test.py", "cases": 12, "fail-fast": true },
"priority": 70 }
JSON
# Job 3 - Code Analysis (smell detection)
cat <<'JSON' | POST /jobs/submit
{ "queue": "code_analysis", "job_type": "code_analysis", "title": "ruff check src/auth",
"payload": { "path": "src/auth/oauth2.py", "linters": ["ruff","pyright"], "fail_on_error": true },
"priority": 40 }
JSON
```
Controlla lo stato in **Jobs List** (sidebar → **Jobs (Activity)** o vai a `http://localhost:3000/jobs`):
- Auto-refresh ogni 3 secondi, 6 stat cards in alto (Running / Queued / Completed / Failed)
- Clicca una riga → modale **Detail JSON** per vedere `result`, `usage` (cpu_seconds, input/output tokens, cost).
- **Enqueue Modal** (top-right) per creare altri job dal frontend.
Dopo 5-10s gli echo handler (`EchoHandler`) completeranno i jobs → status **COMPLETED** con body `{ echo: …, handled_by: worker_id, timestamp: … }`.
### Verifica API count_by_status:
```bash
GET /jobs | python -c '
import json,sys
from collections import Counter
d = json.load(sys.stdin)
print("Jobs totali:", len(d))
for s, c in Counter(j.get("status","?") for j in d).most_common(): print(f" {s:12s} {c}")
'
```
---
## ✅ Step 7 — Code Analysis: Carica Source + Mark Smell come Fixed
Apri `/code` (sidebar **Code Analysis**). La pagina usa **2 useQuery separate**:
1. `type=source_file` (tutti i sorgenti caricati)
2. `type=ai_analysis` filtrato client-side `metadata.kind == "code_smell"` (code smell)
Carichiamo un file Python con 1 smell **ASF-SEC-203** (lo stesso della security finding), poi cliccheremo **Mark as fixed** nel frontend.
```bash
SRC=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{ "project_id": "$PROJ_ID", "type": "source_file",
"external_id": "SRC-AUTH-OAUTH",
"title": "src/auth/oauth2.py",
"status": "active", "source": "git",
"metadata": { "path": "src/auth/oauth2.py", "language": "python",
"loc": 214, "framework": "fastapi+authlib",
"lines_of_code": { "python": 190, "docstring": 42 } } }
JSON
SM=$(cat <<JSON | POST /artifacts | python -c 'import json,sys; print(json.load(sys.stdin)["id"])')
{ "project_id": "$PROJ_ID", "type": "ai_analysis",
"external_id": "CS-031-OAUTH",
"title": "CSRF via weak OAuth state token (secrets 8B instead of 32B)",
"status": "open", "source": "asf-ai",
"metadata": { "kind": "code_smell", "severity": "high", "rule": "ASF-SEC-203",
"confidence": 0.91,
"source_path": "src/auth/oauth2.py",
"lines_to_highlight": [48, 49, 50],
"remediation": "Cambia secrets.token_urlsafe(8) → token_urlsafe(32). Aggiungi test con entropy check.",
"linter": "asf-static-analyzer v0.3",
"affected_lines_count": 3 } }
JSON
# Link: smell è generated_from source file
cat <<JSON | POST /relationships
{ "relationship_type": "generated_from", "source_artifact_id": "$SM", "target_artifact_id": "$SRC" }
JSON
echo "SRC=$SRC SM=$SM"
```
Ora:
1. Apri `http://localhost:3000/code`
2. Filter **Scope → Smells** (solo smell) oppure **Severity → High**
3. Clicca la riga con titolo "CSRF via weak OAuth state token…"
4. A dx nella header del DetailView vedi il pulsante verde **Mark as fixed** → click
5. Attendi ~300ms (useMutation PATCH `status=fixed`, poi invalidateQueries di 2 cache chiavi `code-source` + `code-smells`)
6. Il pulsante sparisce, appare Badge verde **🟢 Resolved**
Verifica con curl che lo stato è persisted in Mongo:
```bash
GET /artifacts/$SM | python -c 'import json,sys; d=json.load(sys.stdin); print("status:", d["status"], "| updated:", d["updated_at"])'
# → status: fixed
```
---
## ✅ Step 8 — Usa Ask ASF per interrogare il tuo grafo (AI)
> 🤖 **ASF AI Gateway: ASTRAZIONE PURA (regola §1)**.
> - Frontend + Backend (asf-core) NON conoscono il provider LLM: fanno richiesta all'AI Gateway.
> - Nessun accoppiamento diretto a OpenAI / Anthropic / Azure OpenAI / ecc.
> - I provider configurabili (già astrazione pronta): **NVIDIA DGX Spark** (on-prem) · OpenAI-compatibili · custom provider.
> - Futuri: model routing per task · AI policies per-tenant · usage metering · priority queues · cost tracking.
> - Nel MVP seed attuale (nessun provider collegato) viene usato `MockProvider`/fallback text per verificare il ciclo 202 + poll + bolle.
Sidebar → **Ask ASF (AI)** oppure `http://localhost:3000/ask-asf`.
Scrivi nel prompt:
> "Cosa causa l'incidente INC-5? Qual è il requisito a monte? Elenca i passaggi dal deploy fino al requirement."
- Il frontend fa **POST /api/v1/ask-asf/query** → HTTP **202 Accepted** (ritorna job_id)
- **Polling ogni 2s per max 120s** (useEffect + cleanup on rerender + cancel ref)
- Quando `status == COMPLETED`: pickAnswer legge `job.result.{message,answer,summary,response,text}` (ordine fallback)
- **JobMeta pills**: queue, job_type, status, timestamps (copy/clear/Enter send, Shift+Enter newline)
In modalità demo (senza provider LLM collegato) l'AI handler ritorna una risposta fallback, ma il **ciclo di Job + 202 + poll + render bolle è completamente funzionale e verificato**.
---
## 🎁 Bonus: Seed Ricreabile Completo
Per ricostruire TUTTO in 1 comando (contenuto demo esistente):
```bash
make seed-clean
```
Questo esegue `bash scripts/seed-demo.sh --clean --jobs` che:
1. **DELETE /artifacts/{id}** di tutti gli esistenti (loop safe, bulk non esiste MVP)
2. **PATCH CANCEL** di tutti i jobs ancora QUEUED/RUNNING
3. POST degli 8 artifact demo (REQ/TASK/PR/BUILD/DEP/TEST/DOC/COMMIT/SEC/INC + 3 Source files + 3 Smells)
4. POST delle 9 relazioni demo
5. POST 5 jobs echo per code/test/devops/indexing/notifications
6. Report finale: counts artifacts by type, graph nodes/edges, jobs status counts
Per rieseguire senza cancellare niente (append di un nuovo set):
```bash
make seed
```
---
## 🗂️ Quick Reference API (Cheat Sheet)
| Endpoint REST | Metodo | Cosa fa |
|---|---|---|
| `/api/v1/organizations` | GET · POST | Lista / Crea tenant |
| `/api/v1/organizations/slug/{s}` | GET | Org per slug |
| `/api/v1/organizations/{id}` | GET · PATCH | Dettaglio / Aggiorna tenant |
| `/api/v1/projects` | GET · POST | Lista / Crea progetto |
| `/api/v1/projects/{id}` | GET · PATCH | Dettaglio / Aggiorna progetto |
| `/api/v1/artifacts?type=&status=&project_id=&limit=&offset=` | GET | Lista (filtri query) |
| `/api/v1/artifacts` | **POST** | Crea artifact (**required: project_id, type, title**) |
| `/api/v1/artifacts/{id}` | GET · **PATCH** · DELETE | Dettaglio · Aggiorna (es. `status=fixed`) · Cancella |
| `/api/v1/relationships` | **POST** | Crea edge (source/type/target) |
| `/api/v1/relationships/{id}` | DELETE | Rimuovi edge |
| `/api/v1/relationships/for_artifact/{aid}` | GET | Lista edge in/out di 1 artifact |
| `/api/v1/relationships/outgoing/{aid}` · `/incoming/{aid}` | GET | Solo uscenti / entranti |
| `/api/v1/traceability/graph?project_id=…` | GET | Nodes + edges per ReactFlow (globale) |
| `/api/v1/jobs` | GET | Lista jobs by tenant |
| `/api/v1/jobs/submit` | **POST** | Submit job `{queue, job_type, title, payload?, priority?}` |
| `/api/v1/jobs/{id}` | GET · PATCH | Dettaglio / Cancella (status=cancelled) |
| `/api/v1/overview` | GET | Metriche Dashboard |
| `/api/v1/audit?limit=100` | GET | Storico eventi (newest first) |
| `/api/v1/ask-asf/query` | **POST** | Submit domanda AI → job con polling 202 |
---
## 📔 Appendice A. Frontend Shell Enterprise (Bootstrap §9)
### 14 Voci di Navigazione previste (shell enterprise)
| # | Route / Sidebar Label | Descrizione | Stato MVP |
|---|---|---|---|
| 1 | `/` Overview | Dashboard aggregata 9 cards | ✅ attiva |
| 2 | `/requirements` | Requirements list + 4 filtri (project / status / type / search) | ✅ attiva · 6 artifacts demo |
| 3 | `/context` Context Explorer | Picker · 5 tabs: Metadata · Relationships · Related (2-hop) · **Graph 3-anelli** · History timeline | ✅ attiva |
| 4 | `/code` Code Analysis | 2 useQuery (source + ai_analysis.kind=code_smell) · 6 filters · 5 StatCards · DetailView con **Mark-as-fixed PATCH** | ✅ attiva |
| 5 | `/traceability` | React Flow layered · nodes 21 · edges 9 · Inspector · refetch 5s | ✅ attiva |
| 6 | **`/ask-asf` Ask ASF (AI)** | Chat UI bolle user/AI · POST /ask-asf/query 202 + poll 2s max 120 · JobMeta pills | ✅ attiva |
| 7 | **`/jobs` Jobs (Activity)** | 6 stats · list · auto-refresh 3s · Modal Enqueue · Detail JSON · Progress bar | ✅ attiva |
| 8 | Pipelines | CI/CD pipeline view | ⏭️ placeholder futuro |
| 9 | Deployments | Deployment history, env, rollback | ⏭️ placeholder |
| 10 | Quality | Quality gates, coverage, lints | ⏭️ placeholder |
| 11 | Observability | Logs/metrics/traces explorer | ⏭️ placeholder |
| 12 | Integrations | Git / CI / Docs / PagerDuty connectors | ⏭️ placeholder |
| 13 | Usage | Tenant AI/job consumption + cost tracking | ⏭️ placeholder |
| 14 | Settings | Org / Team / Project config · keys · SSO · members | ⏭️ placeholder |
### Overview Dashboard — 9 Cards specificate
La home `/` aggrega metriche cross-project per tenant:
| Card | Cosa misura | Stato |
|---|---|---|
| 1 | Project Health (%) · pesato per requirements coverage / deploy success / security backlog | ✅ attiva (94.5% demo) |
| 2 | Requirements count · by status / by type (open/in-progress/done) | ✅ attiva |
| 3 | Implementation Coverage · % requirements con ≥1 task linked | ✅ attiva |
| 4 | Test Coverage · % PR/deploy con ≥1 test verifies collegato | ✅ attiva |
| 5 | Traceability completeness · % artifacts coinvolti in catene Req→Deploy | ✅ attiva |
| 6 | Deployments (last 7d) · count + success rate | ✅ attiva |
| 7 | Active AI Jobs · queued/running · usage last hour | ✅ attiva |
| 8 | Failed Builds (last 7d) | ✅ attiva |
| 9 | Security Findings (open) · per severity (critical/high/medium/low) | ✅ attiva |
---
## 🛡️ Appendice B. Non-Functional Requirements (Bootstrap §12-16)
Linee guida ufficiali del progetto ASF — rispettate in ogni parte del codice:
### 🔎 Osservabilità (§12)
- **Log strutturati JSON**: ogni log include `tenant_id`, `project_id` (se presente), `correlation_id`, `job_id` (se in un job).
- **Correlation ID**: propagato **per tutta la distribuzione** (REST → Rabbit → Worker → AI Gateway → risposta). Middleware di correlazione già attivo.
- **OpenTelemetry-ready**: punti di hook per tracing distribuito (non MVP, boundary presenti).
- **❌ VIETATO loggare**: password, token, API keys, Authorization header, cookie firma, PII (personally identifiable information).
### 🔒 Sicurezza (§14)
1. **Tenant Isolation**: *TUTTE* le query filtrano per `tenant_id` (Mongo). Mai mescolare dati tra tenant.
2. **Input Validation**: TUTTI gli endpoint usano Pydantic v2 validato server-side (body, query params, headers).
3. **Secrets SOLO in env variables**: **MAI hardcodare** URI MongoDB / Rabbit / API keys nel codice o in file di config commitati. `.env.example` documenta le variabili richieste.
4. **Authorization boundaries**: layer Org → Teams → Projects (futuro) preparati per RBAC fine-grained.
5. **Secure defaults**: no permessi wildcard default, CORS whitelist minimale, cookie HttpOnly in authn futura.
6. **Audit logging**: ogni modifica (create/update/delete) su artifacts/projects/organizations/jobs genera record in `audit_events` con `actor_id` (user) + delta + timestamp.
### 👩‍💻 Regole Ingegneristiche (§15 — ASF Engineering Rules)
| Regola | Applicazione pratica |
|---|---|
| **SOLID ove utile** | Service in asf-core/services/* + DI pattern (no singleton globali) |
| **Codice semplice > codice intelligente** | No pattern gang-of-four non necessari |
| **Typed models everywhere** | Pydantic v2 per input/output endpoint |
| **Componenti testabili** | In-memory fakes (FakeJobsCollection / FakeRelService / FakeRepos) invece che Mongo reale per unit test |
| **NO premature optimization** | Ottimizza solo dopo misurazione |
| **NO microservizi per apparenza** | Monolite pragmatico → servizi reali, non finti. asf-core / asf-context / asf-ai / asf-worker = servizi distinti ma acoplamento debole. |
| **NO classi giganti / file 2000 righe** | Split su service/repo/router |
| **NO stato globale nascosto** | Dependencies injection, mutable state solo in MongoDB/Rabbit/Redis (Redis non MVP) |
| **NO provider logic nel domain layer** | asf-domain è package standalone puro: non importa FastAPI, Motor, Rabbit, OpenAI SDK |
### 🧪 Testing Strategy (§16)
- **pytest** come framework unico (tutti i servizi hanno pytest.ini `asyncio_mode=auto` per test async).
- **Test Unit = NO servizi esterni**: Mongo/Rabbit/Redis sostituiti da fakes in-memory. Oggi 40/40 tests VERDI.
- **Services testati oggi**: asf-domain 12 · asf-core 6 · asf-worker 8 · asf-ai 7 · asf-context 4.
- Copertura domini critica: artifact lifecycle, relationship traversal, job claim atomic, AI provider abstraction, context BFS.
---
## ❌ Errori Comuni & Soluzioni
| Errore | Causa | Fix |
|---|---|---|
| **422 `"slug" regex mismatch`** | Org slug con maiuscole / underscore | Usa minuscolo, `-`, 3-60 caratteri, inizia/finisce alfanum |
| **422 `"key" regex mismatch`** | Project key non inizia con maiuscola | `^[A-Z][A-Z0-9]{1,19}$` es. `ASF`, `PROJ2`, non `asf` o `1PROJ` |
| **422 `project_id` required field missing** | Artifact POST senza `project_id` | Aggiungi `"project_id": "<UUID>"` (tenant from headers, project nel body) |
| **422 field `description` / `version` extra_forbidden** | `ArtifactCreate` non ha questi campi | Usa `content=` per corpo lungo, version è solo nel record (non in create) |
| **422 `artifact_type invalid value` (es. code_smell)** | Enum `ArtifactType` non contiene code_smell | Usa `type: "ai_analysis"` + `metadata.kind: "code_smell"` (vedi Step 7) |
| **422 `source_project_id` extra_forbidden in Relationship** | Non si passano project ids nel body | Rimuovi: validazione server-side da artifact sorgente/destinazione |
| **Lista artifacts vuota** | Header `X-ASF-Tenant-Id` mancante | Aggiungi `X-ASF-Tenant-Id: t-demo-003` e `X-ASF-User-Id: u-mario` a tutte le richieste |
| **Context Graph vuoto** | Relazioni 0 sul seed corrente | Ricorreggi Step 4 e assicurati di aver POSTed le relationships con gli UUID corretti |
| **Jobs non completano** | Worker non in esecuzione | `make start` (--with-worker) · controlla `make status` e tail log |
| **TypeScript build fallisce** | Aggiunte imports in usages wrong | `make tsc` diagnostica riga+colonna precise, correggi e rilancia |
| **⚠️ Credenziali esposte in repo** | URI Mongo/Rabbit/API keys hardcodate nel codice invece che in `.env` | **MAI committare `.env` nel repo** · tutte le secrets in env variables · `.env.example` pubblico solo i nomi delle variabili |
| **⚠️ Jobs status persiste male** | Developer ha salvato lo stato del job in RabbitMQ invece che in Mongo | Regola §7: **Mongo = stato permanente**. Rabbit = solo coda messaggi. Se Rabbit cade, lo stato dei job (queued/running/completed/failed) resta su Mongo. |
| **⚠️ Artifacts mixati tra progetti** | Filtro GET `/artifacts` senza `project_id` query param | Aggiungi `&project_id=<UUID>` per vedere solo artifact di 1 progetto specifico · il default mostra TUTTI gli artifact del tenant |
---
## ✅ Pronti! Checklist Primo Progetto Funzionale
Segnando una spunta per ogni step raggiungi la parità con la demo seed:
- [ ] **0. Stack running** (`make status` core=8000 / web=3000 / worker alive)
- [ ] **1. Organization creata** (slug OK, API `/slug` returns 200)
- [ ] **2. Project creato** (key `ASF` o simile, Lista projects restituisce 1+ rows)
- [ ] **3. 4+ artifacts base** (REQ / TASK / PR / TEST)
- [ ] **4. 3+ relationships** → **Context Graph** mostra 3 anelli: selected / hop1 / hop2
- [ ] **5. Traceability globale** mostra 8+/7+ node/edge (BUILD/DEP/SEC/INC aggiunti)
- [ ] **6. 3+ jobs enqueued + COMPLETED** (Jobs page counter "Completed" aumenta)
- [ ] **7. Code Analysis: Smell → Mark as fixed → status="fixed" in GET**
- [ ] **8. Ask ASF → POST 202 → poll → bolle UI renderizzate**
- [ ] **9. `make lint` passa** (40/40 pytest + tsc 0 errors)
**Fine tutorial.** Da qui puoi: (a) estendere i tipi artifact con `metadata.*` libero, (b) aggiungere relazioni a piacere, (c) scrivere handlers custom nel worker per nuovi job, (d) integrare un vero provider LLM in `asf-ai`.