E-13 Stockage objet
Brique E: Données, RAG, contexte, mémoire. Version anglaise: E-13-object-storage
1. Rôle et responsabilités
Stockage S3-compatible validé (SeaweedFS, Ceph RGW, S3, GCS interop) pour documents, médias, exports et snapshots, chiffré.
- Buckets par tenant ou préfixe
- Chiffrement SSE avec clés E-04
- Object Lock pour l'audit WORM
2. Position dans l'architecture et interactions
- Appelants: Media (F), Document Processing, Audit export, Qdrant snapshots.
- Chemin d'exécution: Synchrone (URL présignées).
- Stockage du client : un profil de stockage (
/v1/storage/profiles) désigne le bucket ou conteneur choisi par le client (AWS S3, Azure Blob, Google Cloud Storage, IBM COS, SeaweedFS ou autre service compatible S3) ; les adaptateurseai/storage/parlent l'API REST de chaque fournisseur à travers la garde de sortieeai.security.egress, sans SDK fournisseur. - L'export WORM de l'audit (G) écrit via le profil
audit_wormdu tenant ou le profil de plateformeEAI_AUDIT_WORM_STORAGE_PROFILE, refusé sans Object Lock vérifié. - Toute interaction passe par l'API Control Plane v1 (
/v1) derrière la passerelle API (D-14).
3. Composant open source et partie développée
- Open source: SeaweedFS / Ceph / S3 compatible
- Développé: Adaptateur, politique de buckets.
- Développé : interface unifiée
eai/storage/base.py(put, get, head, list, delete, presign, multipart, vérification sha256, écriture WORM relue, découverte des capacités) et adaptateurss3.py(SigV4, IBM IAM),azure.py(Shared Key, SAS, Microsoft Entra ID, identité de workload),gcs.py(compte de service, URL signées V4) ; domaineeai/domains/storage/(profils, vérification, URL présignées). - Intégration externe via un adaptateur
eai/integrations/<system>.pyavec repli in-process lorsque l'URL n'est pas configurée.
4. Modèle de données
Tables (schéma eai, RLS tenant_id):
eai.mediaeai.kb_documents(object_key)eai.storage_profiles(V125 : fournisseur, point de terminaison, région, bucket,credential_refseulement, chiffrement, capacités découvertes,verified_at; profils de plateforme sans tenant)eai.storage_capability_checks(V126 : historique de chaque vérification)eai.storage_presigned_grants(V127 : portée, clé, expiration et sha256 de chaque URL présignée, jamais l'URL)eai.audit_exports.storage_profile_id,worm_qualified(V128)
Colonnes communes : id uuid, tenant_id uuid NOT NULL, created_at, updated_at ; politique RLS current_setting('app.tenant_id', true)::uuid.
5. Contrat
- Contrat appliqué: multimodal-contract
- Schéma JSON:
contracts/v1/multimodal-contract.schema.json - Champs clés:
media_id, object_key, sha256 - Validation :
POST /v1/contracts/{name}/validate; application d'un manifeste :POST /v1/manifests/apply. - Contrat du profil de stockage :
contracts/v1/storage-profile.schema.json(référence d'identifiant seulement, capacités typées{supported, method, detail, checked_at}).
6. Endpoints API
POST /v1/mediaGET /v1/media/{id}DELETE /v1/media/{id}GET,POST /v1/storage/profiles;GET,PATCH,DELETE /v1/storage/profiles/{profile_id}POST /v1/storage/profiles/{profile_id}/verify(découverte des capacités par de vrais appels),GET /v1/storage/profiles/{profile_id}/checksPOST /v1/storage/profiles/{profile_id}/presign(portée read ou write, préfixe de clé, expiration bornée)- Détails et rôles : control-plane-v1 ; guide : storage-administration
Requête POST /v1/media:
{
"filename": "contrat.pdf",
"content_type": "application/pdf",
"size": 482113
}
Réponse:
{
"id": "med_44",
"upload_url": "https://<s3-host>/eai-tenant-a/...",
"status": "pending_upload"
}
7. Exemples SDK
Python:
from enterprise_ai import EnterpriseAI
client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_xxx_secret")
med = client.multimodal.upload(open("contrat.pdf", "rb").read(), "contrat.pdf", "application/pdf")
TypeScript:
import { EnterpriseAI } from "@enterprise-ai/sdk";
const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY });
const med = await client.media.upload({ file, filename: "invoice.pdf" });
Java:
EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.build();
Media med = client.media().upload(Path.of("contrat.pdf"));
Go:
c, _ := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")))
med, _ := c.Media.Upload(ctx, "contrat.pdf")
8. Configuration
- Variables d'environnement:
EAI_S3_ENDPOINT,EAI_S3_BUCKET,EAI_S3_REGION,EAI_DATABASE_URL - Profils de stockage :
EAI_AUDIT_WORM_STORAGE_PROFILE,EAI_STORAGE_CA_BUNDLE,EAI_STORAGE_REQUEST_TIMEOUT_SECONDS,EAI_STORAGE_PRESIGN_MAX_SECONDS,EAI_STORAGE_CAPABILITY_MAX_AGE_SECONDS,EAI_EGRESS_PRIVATE_ALLOWLIST(points de terminaison sur site),EAI_CRED_<NOM>(identifiants des profils de plateforme) ; voir configuration - Clés Helm:
controlPlane.config,controlPlane.secrets
9. Contrôles de sécurité
- Aucun bucket public
- URLs présignées à durée courte
- Aucun secret dans un profil :
credential_refvers OpenBao (tenant) ouenv://EAI_CRED_<NOM>(plateforme), refus 422storage_secret_in_profile - TLS obligatoire hors dev et test (422
storage_tls_required) ; appels sortants par la garde egress uniquement - Capacités jamais supposées : usage refusé en 409
storage_capability_missingsi la capacité n'est pas vérifiée, inconnue ou trop ancienne ; WORM jamais simulé (relecture de la rétention, 502storage_worm_not_honoured) - Décision de politique avant chaque action (
storage.profile.*,storage.presign.*) - Toutes les requêtes portent le tenant issu du credential ; RLS PostgreSQL
- Chaque mutation et décision de politique écrit un événement d'audit chaîné
10. Observabilité
- Métriques Prometheus:
eai_object_bytes_stored,eai_object_requests_total - Traces: spans OpenTelemetry avec conventions sémantiques GenAI (
gen_ai.operation.name,gen_ai.agent.id,gen_ai.tool.name), attributseai.tenant_id,eai.run_id, propagationtraceparent. - Actions d'audit:
media.upload,media.delete - Actions d'audit des profils :
storage.profile.create,storage.profile.update,storage.profile.delete,storage.profile.verify,storage.presign.issue,storage.capability.refused
11. Unités FinOps
- Go-mois stockés, requêtes, egress.
- Émis comme Usage Event (
POST /v1/finops/usage) avec tenant, projet, agent et run.
12. Modes de défaillance et résilience
- Upload multipart reprenable
- Versioning activé
- Stockage injoignable : 502
storage_unreachable; identifiant refusé : 502storage_auth_failed; refus du stockage : 502storage_backend_error(sans secret dans les détails) - Vérification en échec : profil
failedaveclast_error, usages refusés jusqu'à une vérification réussie - Erreurs normalisées (enveloppe error, message_i18n en/fr)
13. Adoption autonome (client d'un client)
- enterprise_ai.multimodal + stockage S3-compatible.
- SDK:
enterprise_ai.privacy / dlp / knowledge_bases / memory / retention / lineage - Déploiement minimal : chart Helm avec
controlPlane.enabled=trueet PostgreSQL ; les composants externes désactivés utilisent leur repli in-process.
14. Tests d'acceptation
- Objet d'un tenant inaccessible à un autre
- Accès inter-tenant refusé
- Erreur renvoyée en fr avec Accept-Language: fr
- Envoi, lecture, métadonnées, suppression et intégrité sha256 par adaptateur (SeaweedFS, Azurite, fake-gcs-server) :
services/control-plane/tests/test_storage_*.py - Découverte des capacités vraie et fausse, refus d'une capacité manquante, URL présignée expirée et hors portée refusées
- Export WORM de l'audit refusé sans Object Lock vérifié, qualifié et non supprimable sur un bucket Object Lock