ADR-0149: Server routes of the B9.3 SDK interfaces (service identity, application audit, approval cancel, storage objects)
Context
The architecture reference (B9.3) names in every SDK identity.assume_service, approval.cancel, audit.record and the storage operations list and delete. No Control Plane route served them: /v1/context/issue issues a context for the caller itself, an approval decision only accepts approve or reject, audit events were written by the server only, and only presigned links existed for objects. The question was asked to the client; with no answer, the recommended option (all four routes) was taken.
Decision
- Service identity:
POST /v1/service-identities/{identity_id}/assumesigns a short lived ExecutionContext (30 to 3600 s) whose subject isservice:<name>. It needs the OpenFGA relationcan_assumeonservice_identity:<id>, written explicitly: no role implies it, administrators included, and a wildcard subject such asuser:*is refused at write time (422authz_wildcard_not_allowed). A policy decisionservice_identity.assumefollows. The effective rights are the intersection of the identity rights and the caller rights (neverplatform_admin, the lower clearance of the two). The caller stayson_behalf_ofin the signed document and is the actor of the audit events. The context is not an API credential: it is verified withPOST /v1/context/verifyor the JWKS and revoked withPOST /v1/context/{context_id}/revoke. - Application audit:
POST /v1/audit/eventsappends an event of theapp.namespace to the tenant hash chain, sealed and exported like the platform events. The server sets the actor,occurred_at, the sequence and the hashes; the client details (at most 16384 bytes, credentials redacted) stay underevidence.details.attributeswithstream: sdk. The model refuses any other field, so a client never writes a platform event. Only the roles that run application code write (neither viewer nor auditor), after the decisionaudit.record. - Cancel:
POST /v1/approvals/{id}/cancelby the requester, a tenant administrator or a security officer. The pending approval becomescancelled, the waiting run fails and the break glass or mandate request is rejected. A refusal is audited in its own transaction. - Objects:
GET /v1/storage/profiles/{id}/objectsandPOST /v1/storage/profiles/{id}/objects/delete, limited to the presign prefixes of the profile, decided by policy (storage.object.list,storage.object.delete) and audited. A profile of WORM audit exports or evidence never deletes through the API (409storage_object_protected). - The four SDKs expose these routes and confirm the result: an answer without the signed token, without the
cancelledstatus, withoutevent_idandhashor withoutdeleted: trueraises an error instead of being read as a success.
Consequences
The app.* events do not replace the server audit of business changes, always written in the transaction of the change. The OpenFGA model gains the service_identity type. The signature of AssumeService (Go) and assumeService (Java) changes; in Java, storage().list() and storage().delete(...) work on objects, profiles stay available through listProfiles() and deleteProfile(...).
Evidence
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.