G-14 Corrélation d'audit
Brique G de la plateforme Enterprise AI. Technologie : composant développé qui relie les événements d'audit, les décisions de politique, les spans et les événements d'usage par trace_id, run_id et request_id. Version anglaise : ../../en/bricks/G-14-audit-correlation.md.
Rôle et responsabilités
- Répondre à ce qui s'est passé dans ce run : décisions, outils, modèles, données, coût, sur une seule chronologie.
- Fournir des dossiers de preuves pour les incidents et les audits.
Position et interactions
- Chemin d'exécution : chemin de lecture, investigation.
- Utilisé par la vue run du portail, les incidents (brique A) et les auditeurs.
Part open source et part développée
- Open source réutilisé : Aucun : jointures SQL et ClickHouse.
- Développé dans la plateforme : Composant complet : clés de corrélation, API de chronologie, export avec empreintes.
Modèle de données
Tables du Control Plane (schéma eai) : eai.audit_events, eai.telemetry_spans, eai.usage_events.
Chaque table de tenant a tenant_id uuid NOT NULL et une politique de sécurité au niveau des lignes sur current_setting('app.tenant_id', true)::uuid. ClickHouse usage_events est la réplique analytique de eai.usage_events (ReplacingMergeTree par event_id).
Contrat : contracts/v1/audit-event.schema.json (schema_version 1.0). L'usage est toujours remonté avec contracts/v1/usage-event.schema.json, l'audit avec contracts/v1/audit-event.schema.json.
Endpoints d'API
| Méthode | Chemin |
|---|---|
| GET | /v1/audit/correlations |
| GET | /v1/telemetry/traces/{trace_id} |
| GET | /v1/finops/usage |
GET /v1/audit/correlations
Réponse :
{
"items": [
{
"trace_id": "4bf92f35...",
"run_id": "run_42",
"events": 14,
"actions": [
"policy.evaluate",
"tool.invoke",
"model.invoke"
],
"decisions": {
"allow": 13,
"deny": 1
}
}
],
"next_cursor": null
}
GET /v1/telemetry/traces/{trace_id}
Réponse :
{
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"spans": [
{
"span_id": "00f067aa0ba902b7",
"name": "gen_ai.chat",
"duration_ms": 1820
}
],
"links": {
"opik": {
"url": "https://<opik-host>/default/redirect/projects?name=eai-platform",
"metadata_key": "eai.trace_id",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}
}
GET /v1/finops/usage
Réponse :
{
"items": [
{
"event_id": "use_01J",
"usage_type": "llm",
"cost": {
"measured": {
"amount": "0.000228",
"currency": "USD"
}
}
}
],
"next_cursor": null
}
Exemples SDK
Python
from enterprise_ai import EnterpriseAI
client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_live_xxx", tenant="acme", locale="fr")
client.audit.correlations(run_id="run_42")
client.telemetry.trace("4bf92f3577b34da6a3ce929d0e0e4736")
client.finops.usage(agent_id="agt_ap", limit=100)
TypeScript
import { EnterpriseAI } from "@enterprise-ai/sdk";
const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY, tenant: "acme", locale: "fr" });
await client.audit.correlations({ run_id: "run_42" });
await client.telemetry.trace("4bf92f3577b34da6a3ce929d0e0e4736");
await client.finops.usage.list({ agent_id: "agt_ap", limit: 100 });
Java
EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.tenant("acme")
.locale(Locale.FRENCH)
.build();
client.audit().correlations(Map.of("run_id", "run_42"));
client.telemetry().trace("4bf92f3577b34da6a3ce929d0e0e4736");
client.finops().listUsage(Map.of("agent_id", "agt_ap", "limit", 100));
Go
client, err := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")), eai.WithTenant("acme"), eai.WithLocale("fr"))
if err != nil { log.Fatal(err) }
ctx := context.Background()
_, err = client.Audit.Correlations(ctx, eai.WithQuery(map[string]string{"run_id": "run_42"}))
_, err = client.Telemetry.Trace(ctx, "4bf92f3577b34da6a3ce929d0e0e4736")
_, err = client.FinOps.ListUsage(ctx, eai.WithQuery(map[string]string{"agent_id": "agt_ap"}), eai.WithLimit(100))
Configuration
Variables d'environnement : EAI_CLICKHOUSE_URL, EAI_S3_BUCKET.
Clés de valeurs Helm (deploy/helm/enterprise-ai/values.yaml) : controlPlane.config.
Sécurité
- Respecte la RLS et l'habilitation de l'appelant : un contenu de classification supérieure est résumé, pas affiché.
- Tenant déduit de l'identifiant d'accès, jamais du corps ; sécurité au niveau des lignes PostgreSQL sur chaque table de tenant.
- Chaque mutation et décision de politique écrit un événement d'audit chaîné par hachage (G-11).
- Les erreurs utilisent l'enveloppe standard avec message_i18n en et fr.
Observabilité
- Latence des requêtes de corrélation.
- traceparent W3C propagé, X-Request-Id renvoyé, les spans portent eai.tenant_id, eai.project, eai.agent_id, eai.run_id.
Unités FinOps
- La corrélation affiche le coût mesuré par run issu des Usage Events.
Modes de défaillance et résilience
- Ids de trace absents (clients anciens) -> repli sur request_id et fenêtre temporelle.
Adoption autonome
Les auditeurs l'utilisent en lecture seule via le rôle auditor.
Tests d'acceptation
- Un run avec un appel d'outil refusé affiche
decisions allow=13 deny=1et l'usage correspondant.
Accès audités et preuves de décision (section 11 du kit)
- Chaque lecture, recherche, vérification et export de l'audit (
/v1/audit/*,/v1/evidence/*) écrit une ligneeai.audit_access_log(qui, quelle requête, combien de résultats) et son propre événementaudit.read,audit.search,audit.verify,audit.export.readouaudit.payload.decrypt. - Les décisions du PEP enregistrées dans l'événement portent
decision_id,policy_id,policy_version,engine,reasons,obligationset, lorsque le moteur la fournit,bundle_revision. - Les champs
data(classification, DLP, masquage, sortie) etversions(modèle, prompt, outil, skill, plugin) sont chaînés avec l'événement.