Aller au contenu principal

ADR-0149 : Routes serveur des interfaces SDK B9.3 (identité de service, audit applicatif, annulation d'approbation, objets de stockage)

Contexte​

La référence d'architecture (B9.3) nomme dans chaque SDK identity.assume_service, approval.cancel, audit.record et les opérations list et delete du stockage. Aucune route du Control Plane ne les servait : /v1/context/issue émet un contexte pour l'appelant lui-même, la décision d'une approbation n'accepte que approve ou reject, l'audit n'était écrit que par le serveur et seuls les liens présignés existaient pour les objets. La question a été posée au client ; faute de réponse, l'option recommandée (les quatre routes) a été retenue.

Décision​

  • Identité de service : POST /v1/service-identities/{identity_id}/assume signe un ExecutionContext court (30 à 3600 s) dont le sujet est service:<nom>. Il faut la relation OpenFGA can_assume sur service_identity:<id>, écrite explicitement : aucun rôle ne l'implique, administrateurs compris, et un sujet générique comme user:* est refusé à l'écriture (422 authz_wildcard_not_allowed). Une décision de politique service_identity.assume suit. Les droits effectifs sont l'intersection de ceux de l'identité et de ceux de l'appelant (jamais platform_admin, habilitation la plus basse des deux). L'appelant reste on_behalf_of dans le document signé et acteur des événements d'audit. Le contexte n'est pas un identifiant d'API : il se vérifie par POST /v1/context/verify ou le JWKS et se révoque par POST /v1/context/{context_id}/revoke.
  • Audit applicatif : POST /v1/audit/events ajoute un événement de l'espace de noms app. à la chaîne de hachage du tenant, scellée et exportée comme les événements de la plateforme. Le serveur fixe l'acteur, occurred_at, la séquence et les empreintes ; les détails du client (16384 octets au plus, secrets masqués) restent sous evidence.details.attributes avec stream: sdk. Le modèle refuse tout autre champ, donc un client n'écrit jamais un événement de la plateforme. Seuls les rôles qui produisent du code applicatif écrivent (ni viewer ni auditor), après la décision audit.record.
  • Annulation : POST /v1/approvals/{id}/cancel par le demandeur, un administrateur du tenant ou un responsable sécurité. L'approbation en attente passe à cancelled, le run en attente échoue et la demande de break glass ou de mandat est rejetée. Un refus est audité dans sa propre transaction.
  • Objets : GET /v1/storage/profiles/{id}/objects et POST /v1/storage/profiles/{id}/objects/delete, limités aux préfixes présignés du profil, décidés par politique (storage.object.list, storage.object.delete) et audités. Un profil d'exports d'audit WORM ou de preuves ne supprime jamais par l'API (409 storage_object_protected).
  • Les quatre SDK exposent ces routes et confirment le résultat : une réponse sans jeton signé, sans statut cancelled, sans event_id et hash ou sans deleted: true lève une erreur au lieu d'être lue comme un succès.

Conséquences​

Les événements app.* ne remplacent pas l'audit serveur des changements métier, toujours écrit dans la transaction du changement. Le modèle OpenFGA gagne le type service_identity. La signature de AssumeService (Go) et de assumeService (Java) change ; en Java, storage().list() et storage().delete(...) portent sur les objets, les profils restent accessibles par listProfiles() et deleteProfile(...).

Preuves​

services/control-plane/tests/test_b93_server_routes.py, services/control-plane/tests/test_platform_openfga.py, sdks/python/tests/test_sdk_interfaces.py, sdks/typescript/packages/sdk/test/interfaces.test.ts, sdks/go/interfaces_b93_test.go, sdks/java/enterprise-ai-java/src/test/java/io/enterpriseai/interfaces/SdkInterfacesTest.java.