34 KiB
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):
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 │ │ │
└──────────────────────────────────────────────────────────────────┘
- 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).
- 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.
- Project · dentro una Org (e opzionalmente Team), es.
ASF(il prodotto/servizio). Ogni Artifact appartiene SEMPRE a un Project (campoproject_idobbligatorio nelArtifactCreate). - Artifact (17 tipi, enum
ArtifactType— unica sorgente di verità condivisa packageasf-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].
- Nessun modello separato per tipo (regola §2 estensibilità). Ogni tipo è esteso col campo libero
- 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:
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).
cat <<'JSON' | POST /organizations
{
"name": "Azurian Labs Demo",
"slug": "azurian-labs-demo",
"plan": "pro",
"is_active": true
}
JSON
Attesa → HTTP 201, risposta simile a:
{
"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:
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:
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.
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:
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:
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:
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> targetTASK implements REQUIREMENTPR generated_from TASKTEST verifies PR
- Non serve passare
tenant_id,source_project_id,target_project_id(validati server-side).
# 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.
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=queuedorunningsu 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):
# 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:
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:
type=source_file(tutti i sorgenti caricati)type=ai_analysisfiltrato client-sidemetadata.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.
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:
- Apri
http://localhost:3000/code - Filter Scope → Smells (solo smell) oppure Severity → High
- Clicca la riga con titolo "CSRF via weak OAuth state token…"
- A dx nella header del DetailView vedi il pulsante verde Mark as fixed → click
- Attendi ~300ms (useMutation PATCH
status=fixed, poi invalidateQueries di 2 cache chiavicode-source+code-smells) - Il pulsante sparisce, appare Badge verde 🟢 Resolved
Verifica con curl che lo stato è persisted in Mongo:
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 leggejob.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):
make seed-clean
Questo esegue bash scripts/seed-demo.sh --clean --jobs che:
- DELETE /artifacts/{id} di tutti gli esistenti (loop safe, bulk non esiste MVP)
- PATCH CANCEL di tutti i jobs ancora QUEUED/RUNNING
- POST degli 8 artifact demo (REQ/TASK/PR/BUILD/DEP/TEST/DOC/COMMIT/SEC/INC + 3 Source files + 3 Smells)
- POST delle 9 relazioni demo
- POST 5 jobs echo per code/test/devops/indexing/notifications
- Report finale: counts artifacts by type, graph nodes/edges, jobs status counts
Per rieseguire senza cancellare niente (append di un nuovo set):
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)
- Tenant Isolation: TUTTE le query filtrano per
tenant_id(Mongo). Mai mescolare dati tra tenant. - Input Validation: TUTTI gli endpoint usano Pydantic v2 validato server-side (body, query params, headers).
- Secrets SOLO in env variables: MAI hardcodare URI MongoDB / Rabbit / API keys nel codice o in file di config commitati.
.env.exampledocumenta le variabili richieste. - Authorization boundaries: layer Org → Teams → Projects (futuro) preparati per RBAC fine-grained.
- Secure defaults: no permessi wildcard default, CORS whitelist minimale, cookie HttpOnly in authn futura.
- Audit logging: ogni modifica (create/update/delete) su artifacts/projects/organizations/jobs genera record in
audit_eventsconactor_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=autoper 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 statuscore=8000 / web=3000 / worker alive) - 1. Organization creata (slug OK, API
/slugreturns 200) - 2. Project creato (key
ASFo 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 lintpassa (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.