Files
asf/TUTORIAL-PRIMO-PROGETTO copy.md
T
2026-09-08 07:21:34 -04:00

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 │ │ │
└──────────────────────────────────────────────────────────────────┘
  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:

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> 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).
# 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=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):

# 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:

  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.

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:

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):

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):

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.