Skip to main content

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}/assume signs a short lived ExecutionContext (30 to 3600 s) whose subject is service:<name>. It needs the OpenFGA relation can_assume on service_identity:<id>, written explicitly: no role implies it, administrators included, and a wildcard subject such as user:* is refused at write time (422 authz_wildcard_not_allowed). A policy decision service_identity.assume follows. The effective rights are the intersection of the identity rights and the caller rights (never platform_admin, the lower clearance of the two). The caller stays on_behalf_of in the signed document and is the actor of the audit events. The context is not an API credential: it is verified with POST /v1/context/verify or the JWKS and revoked with POST /v1/context/{context_id}/revoke.
  • Application audit: POST /v1/audit/events appends an event of the app. 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 under evidence.details.attributes with stream: 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 decision audit.record.
  • Cancel: POST /v1/approvals/{id}/cancel by the requester, a tenant administrator or a security officer. The pending approval becomes cancelled, 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}/objects and POST /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 (409 storage_object_protected).
  • The four SDKs expose these routes and confirm the result: an answer without the signed token, without the cancelled status, without event_id and hash or without deleted: true raises 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.