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}/assumesigne un ExecutionContext court (30 à 3600 s) dont le sujet estservice:<nom>. Il faut la relation OpenFGAcan_assumesurservice_identity:<id>, écrite explicitement : aucun rôle ne l'implique, administrateurs compris, et un sujet générique commeuser:*est refusé à l'écriture (422authz_wildcard_not_allowed). Une décision de politiqueservice_identity.assumesuit. Les droits effectifs sont l'intersection de ceux de l'identité et de ceux de l'appelant (jamaisplatform_admin, habilitation la plus basse des deux). L'appelant resteon_behalf_ofdans le document signé et acteur des événements d'audit. Le contexte n'est pas un identifiant d'API : il se vérifie parPOST /v1/context/verifyou le JWKS et se révoque parPOST /v1/context/{context_id}/revoke. - Audit applicatif :
POST /v1/audit/eventsajoute un événement de l'espace de nomsapp.à 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 sousevidence.details.attributesavecstream: 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écisionaudit.record. - Annulation :
POST /v1/approvals/{id}/cancelpar 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}/objectsetPOST /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 (409storage_object_protected). - Les quatre SDK exposent ces routes et confirment le résultat : une réponse sans jeton signé, sans statut
cancelled, sansevent_idethashou sansdeleted: truelè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.