Aller au contenu principal

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 adaptateurs eai/storage/ parlent l'API REST de chaque fournisseur à travers la garde de sortie eai.security.egress, sans SDK fournisseur.
  • L'export WORM de l'audit (G) écrit via le profil audit_worm du tenant ou le profil de plateforme EAI_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 adaptateurs s3.py (SigV4, IBM IAM), azure.py (Shared Key, SAS, Microsoft Entra ID, identité de workload), gcs.py (compte de service, URL signées V4) ; domaine eai/domains/storage/ (profils, vérification, URL présignées).
  • Intégration externe via un adaptateur eai/integrations/<system>.py avec 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.media
  • eai.kb_documents (object_key)
  • eai.storage_profiles (V125 : fournisseur, point de terminaison, région, bucket, credential_ref seulement, 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/media
  • GET /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}/checks
  • POST /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_ref vers OpenBao (tenant) ou env://EAI_CRED_<NOM> (plateforme), refus 422 storage_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_missing si la capacité n'est pas vérifiée, inconnue ou trop ancienne ; WORM jamais simulé (relecture de la rétention, 502 storage_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), attributs eai.tenant_id, eai.run_id, propagation traceparent.
  • 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é : 502 storage_auth_failed ; refus du stockage : 502 storage_backend_error (sans secret dans les détails)
  • Vérification en échec : profil failed avec last_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=true et PostgreSQL ; les composants externes désactivés utilisent leur repli in-process.

14. Tests d'acceptation​

  1. Objet d'un tenant inaccessible à un autre
  2. Accès inter-tenant refusé
  3. Erreur renvoyée en fr avec Accept-Language: fr
  4. Envoi, lecture, métadonnées, suppression et intégrité sha256 par adaptateur (SeaweedFS, Azurite, fake-gcs-server) : services/control-plane/tests/test_storage_*.py
  5. 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
  6. Export WORM de l'audit refusé sans Object Lock vérifié, qualifié et non supprimable sur un bucket Object Lock