From 1c6857069d1a34b30e7eb25996a3740199f9c83c Mon Sep 17 00:00:00 2001 From: Cassio Farias Machado Date: Wed, 22 Jul 2026 12:49:52 -0300 Subject: [PATCH 1/6] docs: add Multi-tenancy section to all module user guides Add a consistent ## Multi-tenancy section to every module user guide, documenting whether subscriber token exchange is supported, which auth mechanism is used, how to configure it, and links to further reading. Also update the project review-pr skill with two new criteria: - D6: Multi-tenancy support (design check, nice-to-have) - E2a: Multi-tenancy documentation (docs check, required for new modules) Modules updated: destination, agent_memory, dms, print, objectstore, agentgateway, aicore, adms, core/auditlog, core/auditlog_ng, core/data_anonymization, core/telemetry, core/secret_resolver --- .claude/skills/review-pr/SKILL.md | 16 +++- src/sap_cloud_sdk/adms/user-guide.md | 7 ++ src/sap_cloud_sdk/agent_memory/user-guide.md | 86 ++++++------------- src/sap_cloud_sdk/agentgateway/user-guide.md | 16 ++++ src/sap_cloud_sdk/aicore/user-guide.md | 8 ++ src/sap_cloud_sdk/core/auditlog/user-guide.md | 18 ++++ .../core/auditlog_ng/user-guide.md | 8 ++ .../core/data_anonymization/user-guide.md | 7 ++ .../core/secret_resolver/user-guide.md | 7 ++ .../core/telemetry/user-guide.md | 7 ++ src/sap_cloud_sdk/destination/user-guide.md | 28 ++++++ src/sap_cloud_sdk/dms/user-guide.md | 19 ++-- src/sap_cloud_sdk/objectstore/user-guide.md | 8 ++ src/sap_cloud_sdk/print/user-guide.md | 8 ++ 14 files changed, 176 insertions(+), 67 deletions(-) diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md index 68615313..82a9adf8 100644 --- a/.claude/skills/review-pr/SKILL.md +++ b/.claude/skills/review-pr/SKILL.md @@ -7,7 +7,7 @@ compatibility: gh CLI ≥ 2.0, git, GitHub access to SAP/cloud-sdk-python # PR Review: SAP Cloud SDK for Python -Reviews a PR against 23 criteria across 6 sections. Run from the root of the `cloud-sdk-python` repository. +Reviews a PR against 25 criteria across 6 sections. Run from the root of the `cloud-sdk-python` repository. --- @@ -172,6 +172,9 @@ New list/query operations: encapsulate pagination params like existing modules ( **D5: Telemetry instrumentation** New client methods: `@record_metrics(Module.X, Operation.Y)` from `core/telemetry`. New module: constant added to `core/telemetry/module.py` and operations to `core/telemetry/operation.py`. If module is called by other SDK modules: `_telemetry_source: Optional[Module] = None` param present. +**D6: Multi-tenancy support** +Multi-tenancy is a cross-cutting concern for most BTP applications. For new service modules, consider whether the underlying service supports a provider/subscriber model and, if so, whether the module should expose a way for callers to route requests to a subscriber tenant context. This is a **nice-to-have** (⚠️ if absent, not ❌), but must be a conscious decision either way — the `user-guide.md` must document whether support is present, planned, or intentionally out of scope (see E2a). Common patterns are XSUAA-based (subdomain replacement in token URL) and IAS-based (`app_tid` or Destination Service routing), but any reasonable mechanism is acceptable. + --- ### Section E: Tests & Documentation @@ -182,6 +185,15 @@ Every changed `src/` file → corresponding change in `tests/`. Unit: `tests/[mo **E2: Documentation quality** New modules: `user-guide.md` with overview, quick start, config examples, API examples, troubleshooting. Changed public APIs: docstrings updated (Google/NumPy style: `Args:`, `Returns:`, `Raises:`). Sub-audience features not mixed into the general user guide. +**E2a: Multi-tenancy documentation** +Every new or modified `user-guide.md` must contain a `## Multi-tenancy` section with all four fields: +- **Supported:** Yes / No / Not yet / N/A +- **Authentication:** XSUAA / IAS / Other / — +- **How to use:** explanation (or "Not applicable") +- **Further reading:** link(s) or "—" + +If the module exposes XSUAA or IAS authentication, the section must describe the actual mechanism (e.g., `access_strategy` param, `tenant` param, token URL replacement). A placeholder or missing section → ⚠️. If the module is a new service module with no section at all → ❌. + **E3: Module structure compliance** New modules follow: ``` @@ -246,12 +258,14 @@ tests/[module]/integration/ (optional, BDD) | D3 | Breaking changes marked | | | | D4 | Pagination & tenant filtering | | | | D5 | Telemetry instrumentation | | | +| D6 | Multi-tenancy support | | | ### E: Tests & Documentation | # | Criterion | Status | Finding | |---|-----------|--------|---------| | E1 | Tests added/updated | | | | E2 | Documentation quality | | | +| E2a | Multi-tenancy documentation | | | | E3 | Module structure compliance | | | --- diff --git a/src/sap_cloud_sdk/adms/user-guide.md b/src/sap_cloud_sdk/adms/user-guide.md index 0d9facf4..c550a584 100644 --- a/src/sap_cloud_sdk/adms/user-guide.md +++ b/src/sap_cloud_sdk/adms/user-guide.md @@ -244,6 +244,13 @@ activate_input = DraftActivateInput( active = client.relations.activate_draft(activate_input) ``` +## Multi-tenancy + +- **Supported:** No +- **Authentication:** IAS +- **How to use:** Multi-tenancy is not supported by this service. +- **Further reading:** N/A + ## Error Handling ```python diff --git a/src/sap_cloud_sdk/agent_memory/user-guide.md b/src/sap_cloud_sdk/agent_memory/user-guide.md index 3f611771..51207df4 100644 --- a/src/sap_cloud_sdk/agent_memory/user-guide.md +++ b/src/sap_cloud_sdk/agent_memory/user-guide.md @@ -23,10 +23,6 @@ plain text, and the service makes it searchable by meaning. - [`agent_id`](#agent_id) - [`invoker_id`](#invoker_id) - [Multitenancy](#multitenancy) - - [AccessStrategy](#accessstrategy) - - [Configuring at client level](#configuring-at-client-level) - - [SUBSCRIBER (default)](#subscriber-default) - - [PROVIDER](#provider) - [Semantic Search: A Brief Primer](#semantic-search-a-brief-primer) - [Memories](#memories) - [Create a Memory](#create-a-memory) @@ -172,64 +168,36 @@ across create, read, and search calls is the implementer's responsibility. ## Multitenancy -The Agent Memory service runs in a multi-tenant BTP environment. By default, every API -call uses a **subscriber-scoped token** — meaning data is isolated to the subscriber tenant -that your application serves. You control this behaviour with the `access_strategy` and -`tenant` keyword arguments available on every client method. +- **Supported:** Yes +- **Authentication:** XSUAA +- **How to use:** Pass `access_strategy` and `tenant` to `create_client()`. The strategy controls whether calls use a subscriber-scoped or provider-scoped XSUAA token. Every method on the client inherits the strategy set at construction time. -### AccessStrategy + | Value | Description | + | ------------------------------------- | ----------------------------------------------------------------------------------------------- | + | `AccessStrategy.SUBSCRIBER` (default) | Reads and writes against the subscriber tenant. Requires `tenant`. | + | `AccessStrategy.PROVIDER` | Reads and writes against the provider tenant. No `tenant` needed. Provides no tenant isolation. | -```python -from sap_cloud_sdk.agent_memory import AccessStrategy -``` - -| Value | Description | -| --------------------------- | ------------------------------------------------------------------------------------------------------------- | -| `SUBSCRIBER` (default) | Reads and writes against the subscriber tenant. Requires `tenant`. | -| `PROVIDER` | Reads and writes against the provider tenant. No `tenant` needed. Caution: this provides no tenant isolation. | - -### Configuring at client level - -Pass `access_strategy` and `tenant` to `create_client()` to set defaults for the entire -client instance. Every method call then inherits them, so you do not need to repeat them -on each operation. + > [!WARNING] + > `PROVIDER` strategy provides **no tenant isolation**, the provider token grants access to data in the provider subaccount. Only use this strategy for provider-owned operations (e.g., admin tasks, shared datasets). Never use it to serve subscriber-specific data. -```python -from sap_cloud_sdk.agent_memory import create_client, AccessStrategy + ```python + from sap_cloud_sdk.agent_memory import create_client, AccessStrategy -# Tenant set once — all calls below use it automatically -client = create_client( - access_strategy=AccessStrategy.SUBSCRIBER, - tenant="acme-corp", -) + # Subscriber context — all calls use the tenant set here + client = create_client( + access_strategy=AccessStrategy.SUBSCRIBER, + tenant="acme-corp", + ) + memories = client.list_memories(agent_id="some-assistant", invoker_id="user-42") -memories = client.list_memories(agent_id="hr-assistant", invoker_id="user-42") -count = client.count_memories(agent_id="hr-assistant") -``` + # Provider context — no tenant isolation + client = create_client(access_strategy=AccessStrategy.PROVIDER) + memories = client.list_memories(agent_id="some-assistant", invoker_id="user-42") + ``` -### SUBSCRIBER (default) +- **Further reading:** N/A -Configure a subscriber tenant at client creation. All calls will use that tenant context. - -```python -client = create_client( - access_strategy=AccessStrategy.SUBSCRIBER, - tenant="acme-corp", -) -memories = client.list_memories(agent_id="hr-assistant", invoker_id="user-42") -``` - -### PROVIDER - -Configure a provider-only client. No tenant is needed; all calls use the provider binding. - -```python -client = create_client(access_strategy=AccessStrategy.PROVIDER) -memories = client.list_memories(agent_id="hr-assistant", invoker_id="user-42") -``` - -> [!WARNING] -> `PROVIDER` provides **no tenant isolation** — the provider token grants access to data across all subscriber tenants Only use this strategy for provider-owned operations (e.g., admin tasks, shared datasets). Never use it to serve subscriber-specific data. + ## Semantic Search: A Brief Primer @@ -568,10 +536,10 @@ See the [Content and metadata filtering](#content-and-metadata-filtering) note u ### Enums -| Enum | Values | -| ---------------- | -------------------------------------------- | -| `MessageRole` | `USER`, `ASSISTANT`, `SYSTEM`, `TOOL` | -| `AccessStrategy` | `SUBSCRIBER` (default), `PROVIDER` | +| Enum | Values | +| ---------------- | ------------------------------------- | +| `MessageRole` | `USER`, `ASSISTANT`, `SYSTEM`, `TOOL` | +| `AccessStrategy` | `SUBSCRIBER` (default), `PROVIDER` | All models expose a `to_dict()` method that returns a plain dict for logging or forwarding. diff --git a/src/sap_cloud_sdk/agentgateway/user-guide.md b/src/sap_cloud_sdk/agentgateway/user-guide.md index e92488c6..1beadd78 100644 --- a/src/sap_cloud_sdk/agentgateway/user-guide.md +++ b/src/sap_cloud_sdk/agentgateway/user-guide.md @@ -156,6 +156,22 @@ The SDK discovers resources via BTP Destination Service fragments filtered by th | `subscriber.ias` | IAS credential fragment for system-scoped token acquisition | | `subscriber.ias.user` | IAS credential fragment for user-scoped token exchange | +## Multi-tenancy + +- **Supported:** Yes (LoB flow); Partial (Customer flow) +- **Authentication:** IAS (mTLS for Customer flow; IAS via Destination Service for LoB flow) +- **How to use:** + - **LoB flow:** Pass `tenant_subdomain` to `create_client()`. All subsequent calls on that client instance use the subscriber tenant context. + + ```python + from sap_cloud_sdk.agentgateway import create_client + + client = create_client(tenant_subdomain="my-subscriber") + agent_cards = client.list_agent_cards() + ``` + + - **Customer flow:** Pass `app_tid` (the subscriber's BTP application tenant ID) to the individual auth methods (`get_system_auth`, `get_user_auth`, `list_mcp_tools`, `call_mcp_tool`). + ## API ### Factory Function diff --git a/src/sap_cloud_sdk/aicore/user-guide.md b/src/sap_cloud_sdk/aicore/user-guide.md index ea80d3c7..b015caf5 100644 --- a/src/sap_cloud_sdk/aicore/user-guide.md +++ b/src/sap_cloud_sdk/aicore/user-guide.md @@ -390,6 +390,14 @@ set_aicore_config() --- +## Multi-tenancy + +- **Supported:** No +- **Authentication:** XSUAA (written to litellm environment variables) +- **How to use:** Not supported. This module is a process-level bootstrap that writes XSUAA credentials to environment variables consumed by litellm. Multi-tenant routing is out of scope at this layer. +- **Further reading:** + - [SAP AI Core — SAP Help Portal](https://help.sap.com/docs/sap-ai-core) + ## Error Handling Always handle potential configuration errors: diff --git a/src/sap_cloud_sdk/core/auditlog/user-guide.md b/src/sap_cloud_sdk/core/auditlog/user-guide.md index 6edf3fb4..aa23cdef 100644 --- a/src/sap_cloud_sdk/core/auditlog/user-guide.md +++ b/src/sap_cloud_sdk/core/auditlog/user-guide.md @@ -328,6 +328,24 @@ security_event = SecurityEvent( client.log(security_event) ``` +## Multi-tenancy + +- **Supported:** N/A at auth level +- **Authentication:** XSUAA +- **How to use:** Tenant identity is expressed as an event-level field inside each logged event, not at the authentication layer. The provider always authenticates with its own XSUAA token. Pass `Tenant.SUBSCRIBER` or `Tenant.PROVIDER` when constructing events: + + ```python + from sap_cloud_sdk.core.auditlog import Tenant + + event = client.data_modification_event(tenant=Tenant.SUBSCRIBER) + ``` + + > [!NOTE] + > This module (auditlog v2) is deprecated. Use `core/auditlog_ng` for new integrations. + +- **Further reading:** + - [SAP Audit Log Service — SAP Help Portal](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-service) + ## Error Handling Always handle exceptions when logging audit events: diff --git a/src/sap_cloud_sdk/core/auditlog_ng/user-guide.md b/src/sap_cloud_sdk/core/auditlog_ng/user-guide.md index f6d58427..3d68571c 100644 --- a/src/sap_cloud_sdk/core/auditlog_ng/user-guide.md +++ b/src/sap_cloud_sdk/core/auditlog_ng/user-guide.md @@ -274,6 +274,14 @@ with create_client( --- +## Multi-tenancy + +- **Supported:** N/A at auth level +- **Authentication:** None (uses Destination Service / SPII for transport) +- **How to use:** Tenant identity is embedded in each event payload via the `tenant_id` field. Transport and auth are handled by the Destination Service / SPII. This module is only available through SAP for ME. +- **Further reading:** + - [SAP Audit Log Service — SAP Help Portal](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-service) + ## Validation Events are validated against protobuf constraints using `protovalidate` before sending. A `ValueError` is raised if: diff --git a/src/sap_cloud_sdk/core/data_anonymization/user-guide.md b/src/sap_cloud_sdk/core/data_anonymization/user-guide.md index 074667bf..fd9935d6 100644 --- a/src/sap_cloud_sdk/core/data_anonymization/user-guide.md +++ b/src/sap_cloud_sdk/core/data_anonymization/user-guide.md @@ -283,6 +283,13 @@ Typical file pseudonymization responses: | `filename` | `str \| None` | Filename from the `Content-Disposition` header, if present. | | `raw` | `dict` | Parsed JSON payload when available and suitable for response inspection. | +## Multi-tenancy + +- **Supported:** No +- **Authentication:** N/A +- **How to use:** This module has no multi-tenancy model. +- **Further reading:** N/A + ## Error Handling Always catch `DataAnonymizationError` or its subclasses around calls: diff --git a/src/sap_cloud_sdk/core/secret_resolver/user-guide.md b/src/sap_cloud_sdk/core/secret_resolver/user-guide.md index aeea94b6..c7950738 100644 --- a/src/sap_cloud_sdk/core/secret_resolver/user-guide.md +++ b/src/sap_cloud_sdk/core/secret_resolver/user-guide.md @@ -220,6 +220,13 @@ max_retries = int(api_config.retries) --- +## Multi-tenancy + +- **Supported:** N/A +- **Authentication:** N/A +- **How to use:** This is an infrastructure module for reading service bindings. It has no multi-tenancy concept of its own. +- **Further reading:** N/A + ## Error Handling The Secret Resolver handles missing secrets gracefully by leaving default values unchanged: diff --git a/src/sap_cloud_sdk/core/telemetry/user-guide.md b/src/sap_cloud_sdk/core/telemetry/user-guide.md index 019bc1ac..ecdf4550 100644 --- a/src/sap_cloud_sdk/core/telemetry/user-guide.md +++ b/src/sap_cloud_sdk/core/telemetry/user-guide.md @@ -284,6 +284,13 @@ auto_instrument(middlewares=[StarletteIASTelemetryMiddleware(app=app)]) --- +## Multi-tenancy + +- **Supported:** N/A +- **Authentication:** N/A +- **How to use:** This is an infrastructure module. `set_tenant_id()` and `StarletteIASTelemetryMiddleware` allow attaching a tenant identifier to OpenTelemetry spans as metadata, but this is observability context. +- **Further reading:** N/A + ## Configuration ### Production diff --git a/src/sap_cloud_sdk/destination/user-guide.md b/src/sap_cloud_sdk/destination/user-guide.md index f5ec6381..2f9709ca 100644 --- a/src/sap_cloud_sdk/destination/user-guide.md +++ b/src/sap_cloud_sdk/destination/user-guide.md @@ -684,6 +684,34 @@ mocks/certificates.json Entries with a `"tenant"` field are treated as subscriber-specific. Entries without `"tenant"` are provider entries. +## Multi-tenancy + +- **Supported:** Yes +- **Authentication:** XSUAA +- **How to use:** Pass `access_strategy` and `tenant` to the methods. The `AccessStrategy` enum controls whether the SDK fetches a subscriber-scoped or provider-scoped token by replacing the `identityzone` subdomain in the token URL. + + ```python + from sap_cloud_sdk.destination import create_client, AccessStrategy + + client = create_client() + + # Subscriber-first (tries subscriber, falls back to provider) + destinations = client.list_subaccount_destinations( + access_strategy=AccessStrategy.SUBSCRIBER_FIRST, + tenant="my-subscriber", + ) + + # Provider only + destinations = client.list_subaccount_destinations( + access_strategy=AccessStrategy.PROVIDER_ONLY, + ) + ``` + + Available strategies: `SUBSCRIBER_ONLY`, `PROVIDER_ONLY`, `SUBSCRIBER_FIRST`, `PROVIDER_FIRST`. + +- **Further reading:** + - [Multitenancy in the Destination Service — SAP Help Portal](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/4e07f250fe5d441cab09f69e22909198.html) + ## Error Handling - `DestinationNotFoundError`: mapped from HTTP 404 where applicable diff --git a/src/sap_cloud_sdk/dms/user-guide.md b/src/sap_cloud_sdk/dms/user-guide.md index b761f7af..4952e2ca 100644 --- a/src/sap_cloud_sdk/dms/user-guide.md +++ b/src/sap_cloud_sdk/dms/user-guide.md @@ -469,15 +469,20 @@ while True: ## Multi-Tenancy -All operations support an optional `tenant` parameter for subscriber-scoped requests. The SDK resolves the token URL by replacing the provider's identity zone with the tenant subdomain: +- **Supported:** Yes +- **Authentication:** XSUAA +- **How to use:** Pass an optional `tenant` parameter to any operation. The SDK resolves a subscriber-scoped XSUAA token by replacing the provider's `identityzone` subdomain in the token URL. Omitting `tenant` uses the provider token (default). -```python -# Provider context (default) -repos = client.get_all_repositories() + ```python + # Provider context (default) + repos = client.get_all_repositories() -# Subscriber context -repos = client.get_all_repositories(tenant="subscriber-subdomain") -``` + # Subscriber context + repos = client.get_all_repositories(tenant="subscriber-subdomain") + ``` + +- **Further reading:** + - [Multitenancy with SAP Document Management Service — SAP Help Portal](https://help.sap.com/docs/DOCUMENT_MANAGEMENT/f6e70dd4bffa4b65965b43feed4c9429/0f6dd1bbaca342ee9177b9ece3fcaaa3.html) --- diff --git a/src/sap_cloud_sdk/objectstore/user-guide.md b/src/sap_cloud_sdk/objectstore/user-guide.md index 1e7e6698..5864e458 100644 --- a/src/sap_cloud_sdk/objectstore/user-guide.md +++ b/src/sap_cloud_sdk/objectstore/user-guide.md @@ -180,6 +180,14 @@ client.delete_object("non-existent.txt") # This won't raise an error --- +## Multi-tenancy + +- **Supported:** No (Object Store is not multi-tenant aware) +- **Authentication:** N/A +- **How to use:** Multi-tenancy is not supported by this service. Object Store uses static access key credentials. Each service binding is scoped to a single dedicated bucket. To serve multiple tenants, provision a separate service instance per tenant. +- **Further reading:** + - [SAP Object Store Service — SAP Help Portal](https://help.sap.com/docs/object-store) + ## Error Handling The ObjectStore module provides specific exceptions for different error scenarios: diff --git a/src/sap_cloud_sdk/print/user-guide.md b/src/sap_cloud_sdk/print/user-guide.md index 99456258..59b62855 100644 --- a/src/sap_cloud_sdk/print/user-guide.md +++ b/src/sap_cloud_sdk/print/user-guide.md @@ -213,6 +213,14 @@ client.create_print_task(task) --- +## Multi-tenancy + +- **Supported:** Not yet +- **Authentication:** XSUAA +- **How to use:** Multi-tenancy is not yet implemented in this module. +- **Further reading:** + - [SAP Print Service — SAP Help Portal](https://help.sap.com/docs/SCP_PRINT_SERVICE) + ## Error Handling The Print module provides specific exceptions for different error scenarios: From efab17268d76eb67595b2f63bb645d40672fed0f Mon Sep 17 00:00:00 2001 From: Cassio Farias Machado Date: Wed, 22 Jul 2026 12:58:48 -0300 Subject: [PATCH 2/6] chore: fix trailing whitespace in agent_memory user-guide --- src/sap_cloud_sdk/agent_memory/user-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sap_cloud_sdk/agent_memory/user-guide.md b/src/sap_cloud_sdk/agent_memory/user-guide.md index 51207df4..0f692504 100644 --- a/src/sap_cloud_sdk/agent_memory/user-guide.md +++ b/src/sap_cloud_sdk/agent_memory/user-guide.md @@ -197,7 +197,7 @@ across create, read, and search calls is the implementer's responsibility. - **Further reading:** N/A - + ## Semantic Search: A Brief Primer From 55e52fda99fe1eced978c53faf219de7f77e91ef Mon Sep 17 00:00:00 2001 From: Cassio Farias Machado Date: Wed, 22 Jul 2026 14:34:12 -0300 Subject: [PATCH 3/6] docs: add further reading section to Agent Gateway user guide --- src/sap_cloud_sdk/agentgateway/user-guide.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sap_cloud_sdk/agentgateway/user-guide.md b/src/sap_cloud_sdk/agentgateway/user-guide.md index 1beadd78..5c471cd6 100644 --- a/src/sap_cloud_sdk/agentgateway/user-guide.md +++ b/src/sap_cloud_sdk/agentgateway/user-guide.md @@ -172,6 +172,8 @@ The SDK discovers resources via BTP Destination Service fragments filtered by th - **Customer flow:** Pass `app_tid` (the subscriber's BTP application tenant ID) to the individual auth methods (`get_system_auth`, `get_user_auth`, `list_mcp_tools`, `call_mcp_tool`). +- **Further reading:** N/A + ## API ### Factory Function From 36313dae833d47c6cc6742832e9778b3c3a52d23 Mon Sep 17 00:00:00 2001 From: Cassio Farias Machado Date: Wed, 22 Jul 2026 14:45:01 -0300 Subject: [PATCH 4/6] docs: enhance multi-tenancy section in PR review guidelines --- .claude/skills/review-pr/SKILL.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md index 82a9adf8..98874111 100644 --- a/.claude/skills/review-pr/SKILL.md +++ b/.claude/skills/review-pr/SKILL.md @@ -173,7 +173,14 @@ New list/query operations: encapsulate pagination params like existing modules ( New client methods: `@record_metrics(Module.X, Operation.Y)` from `core/telemetry`. New module: constant added to `core/telemetry/module.py` and operations to `core/telemetry/operation.py`. If module is called by other SDK modules: `_telemetry_source: Optional[Module] = None` param present. **D6: Multi-tenancy support** -Multi-tenancy is a cross-cutting concern for most BTP applications. For new service modules, consider whether the underlying service supports a provider/subscriber model and, if so, whether the module should expose a way for callers to route requests to a subscriber tenant context. This is a **nice-to-have** (⚠️ if absent, not ❌), but must be a conscious decision either way — the `user-guide.md` must document whether support is present, planned, or intentionally out of scope (see E2a). Common patterns are XSUAA-based (subdomain replacement in token URL) and IAS-based (`app_tid` or Destination Service routing), but any reasonable mechanism is acceptable. +Multi-tenancy is a cross-cutting concern for most BTP applications. For new service modules, consider the full scope of multi-tenancy — not just token routing, but also provisioning and tenant isolation: + +- **Provisioning:** Does the service require a subscription or onboarding step per tenant? Does the module need to support tenant lifecycle callbacks (subscribe/unsubscribe)? +- **Tenant isolation:** Is data or configuration isolated per tenant at the service level? Does the module enforce or expose tenant boundaries correctly? +- **Auth/routing:** Does the module need to route requests to a subscriber tenant context (e.g., XSUAA subdomain replacement, IAS `app_tid`, or Destination Service routing)? +- **Infrastructure:** Is there any infrastructure work required (e.g., new service binding fields, SPII fragments, Subscription Manager integration)? + +This is a **nice-to-have** (⚠️ if absent, not ❌), but must be a conscious decision either way — the `user-guide.md` must document whether support is present, planned, or intentionally out of scope (see E2a). --- From c402f34b73ee2708eb087cfabfb1fa9c4d984b6f Mon Sep 17 00:00:00 2001 From: Cassio Farias Machado Date: Wed, 22 Jul 2026 14:58:16 -0300 Subject: [PATCH 5/6] docs: update multi-tenancy section with improved formatting and clarifications --- src/sap_cloud_sdk/agentgateway/user-guide.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sap_cloud_sdk/agentgateway/user-guide.md b/src/sap_cloud_sdk/agentgateway/user-guide.md index 5c471cd6..1a422a0d 100644 --- a/src/sap_cloud_sdk/agentgateway/user-guide.md +++ b/src/sap_cloud_sdk/agentgateway/user-guide.md @@ -125,7 +125,7 @@ mcp_tool_to_langchain( The converter maps each property's JSON Schema `"type"` to the corresponding Python type so Pydantic validates and forwards the correct native type to the MCP server: | JSON Schema type | Python type | -|------------------|-------------| +| ---------------- | ----------- | | `"string"` | `str` | | `"integer"` | `int` | | `"number"` | `float` | @@ -149,17 +149,17 @@ The SDK automatically detects the agent type based on the presence of a credenti The SDK discovers resources via BTP Destination Service fragments filtered by the `sap-managed-runtime-type` label: -| Label value | Resource | -|---|---| -| `agw.mcp.server` | MCP tool server — `URL` property points to the MCP endpoint | -| `agw.a2a.server` | A2A agent — `URL` property is the agent base URL; ORD ID is extracted from the second-to-last URL path segment | -| `subscriber.ias` | IAS credential fragment for system-scoped token acquisition | -| `subscriber.ias.user` | IAS credential fragment for user-scoped token exchange | +| Label value | Resource | +| --------------------- | -------------------------------------------------------------------------------------------------------------- | +| `agw.mcp.server` | MCP tool server — `URL` property points to the MCP endpoint | +| `agw.a2a.server` | A2A agent — `URL` property is the agent base URL; ORD ID is extracted from the second-to-last URL path segment | +| `subscriber.ias` | IAS credential fragment for system-scoped token acquisition | +| `subscriber.ias.user` | IAS credential fragment for user-scoped token exchange | ## Multi-tenancy -- **Supported:** Yes (LoB flow); Partial (Customer flow) -- **Authentication:** IAS (mTLS for Customer flow; IAS via Destination Service for LoB flow) +- **Supported:** Yes (LoB flow); N/A (Customer flow) +- **Authentication:** IAS (IAS via Destination Service for LoB flow; mTLS for Customer flow) - **How to use:** - **LoB flow:** Pass `tenant_subdomain` to `create_client()`. All subsequent calls on that client instance use the subscriber tenant context. @@ -170,7 +170,7 @@ The SDK discovers resources via BTP Destination Service fragments filtered by th agent_cards = client.list_agent_cards() ``` - - **Customer flow:** Pass `app_tid` (the subscriber's BTP application tenant ID) to the individual auth methods (`get_system_auth`, `get_user_auth`, `list_mcp_tools`, `call_mcp_tool`). + - **Customer flow:** N/A - **Further reading:** N/A From 210e31a809d041d88a836fa9ff2776438d83d0f3 Mon Sep 17 00:00:00 2001 From: Cassio Farias Machado Date: Wed, 22 Jul 2026 15:17:17 -0300 Subject: [PATCH 6/6] docs: remove example code snippets from user guides for multi-tenancy --- src/sap_cloud_sdk/agent_memory/user-guide.md | 16 --------------- src/sap_cloud_sdk/agentgateway/user-guide.md | 9 --------- src/sap_cloud_sdk/core/auditlog/user-guide.md | 12 +---------- src/sap_cloud_sdk/destination/user-guide.md | 20 ------------------- src/sap_cloud_sdk/dms/user-guide.md | 9 --------- 5 files changed, 1 insertion(+), 65 deletions(-) diff --git a/src/sap_cloud_sdk/agent_memory/user-guide.md b/src/sap_cloud_sdk/agent_memory/user-guide.md index 0f692504..d5c0432d 100644 --- a/src/sap_cloud_sdk/agent_memory/user-guide.md +++ b/src/sap_cloud_sdk/agent_memory/user-guide.md @@ -179,22 +179,6 @@ across create, read, and search calls is the implementer's responsibility. > [!WARNING] > `PROVIDER` strategy provides **no tenant isolation**, the provider token grants access to data in the provider subaccount. Only use this strategy for provider-owned operations (e.g., admin tasks, shared datasets). Never use it to serve subscriber-specific data. - - ```python - from sap_cloud_sdk.agent_memory import create_client, AccessStrategy - - # Subscriber context — all calls use the tenant set here - client = create_client( - access_strategy=AccessStrategy.SUBSCRIBER, - tenant="acme-corp", - ) - memories = client.list_memories(agent_id="some-assistant", invoker_id="user-42") - - # Provider context — no tenant isolation - client = create_client(access_strategy=AccessStrategy.PROVIDER) - memories = client.list_memories(agent_id="some-assistant", invoker_id="user-42") - ``` - - **Further reading:** N/A diff --git a/src/sap_cloud_sdk/agentgateway/user-guide.md b/src/sap_cloud_sdk/agentgateway/user-guide.md index 1a422a0d..c9eec51d 100644 --- a/src/sap_cloud_sdk/agentgateway/user-guide.md +++ b/src/sap_cloud_sdk/agentgateway/user-guide.md @@ -162,16 +162,7 @@ The SDK discovers resources via BTP Destination Service fragments filtered by th - **Authentication:** IAS (IAS via Destination Service for LoB flow; mTLS for Customer flow) - **How to use:** - **LoB flow:** Pass `tenant_subdomain` to `create_client()`. All subsequent calls on that client instance use the subscriber tenant context. - - ```python - from sap_cloud_sdk.agentgateway import create_client - - client = create_client(tenant_subdomain="my-subscriber") - agent_cards = client.list_agent_cards() - ``` - - **Customer flow:** N/A - - **Further reading:** N/A ## API diff --git a/src/sap_cloud_sdk/core/auditlog/user-guide.md b/src/sap_cloud_sdk/core/auditlog/user-guide.md index aa23cdef..84241bb5 100644 --- a/src/sap_cloud_sdk/core/auditlog/user-guide.md +++ b/src/sap_cloud_sdk/core/auditlog/user-guide.md @@ -332,17 +332,7 @@ client.log(security_event) - **Supported:** N/A at auth level - **Authentication:** XSUAA -- **How to use:** Tenant identity is expressed as an event-level field inside each logged event, not at the authentication layer. The provider always authenticates with its own XSUAA token. Pass `Tenant.SUBSCRIBER` or `Tenant.PROVIDER` when constructing events: - - ```python - from sap_cloud_sdk.core.auditlog import Tenant - - event = client.data_modification_event(tenant=Tenant.SUBSCRIBER) - ``` - - > [!NOTE] - > This module (auditlog v2) is deprecated. Use `core/auditlog_ng` for new integrations. - +- **How to use:** Tenant identity is expressed as an event-level field inside each logged event, not at the authentication layer. The provider always authenticates with its own XSUAA token. Pass `Tenant.SUBSCRIBER` or `Tenant.PROVIDER` when constructing events. - **Further reading:** - [SAP Audit Log Service — SAP Help Portal](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-service) diff --git a/src/sap_cloud_sdk/destination/user-guide.md b/src/sap_cloud_sdk/destination/user-guide.md index 2f9709ca..055258de 100644 --- a/src/sap_cloud_sdk/destination/user-guide.md +++ b/src/sap_cloud_sdk/destination/user-guide.md @@ -689,26 +689,6 @@ Entries with a `"tenant"` field are treated as subscriber-specific. Entries with - **Supported:** Yes - **Authentication:** XSUAA - **How to use:** Pass `access_strategy` and `tenant` to the methods. The `AccessStrategy` enum controls whether the SDK fetches a subscriber-scoped or provider-scoped token by replacing the `identityzone` subdomain in the token URL. - - ```python - from sap_cloud_sdk.destination import create_client, AccessStrategy - - client = create_client() - - # Subscriber-first (tries subscriber, falls back to provider) - destinations = client.list_subaccount_destinations( - access_strategy=AccessStrategy.SUBSCRIBER_FIRST, - tenant="my-subscriber", - ) - - # Provider only - destinations = client.list_subaccount_destinations( - access_strategy=AccessStrategy.PROVIDER_ONLY, - ) - ``` - - Available strategies: `SUBSCRIBER_ONLY`, `PROVIDER_ONLY`, `SUBSCRIBER_FIRST`, `PROVIDER_FIRST`. - - **Further reading:** - [Multitenancy in the Destination Service — SAP Help Portal](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/4e07f250fe5d441cab09f69e22909198.html) diff --git a/src/sap_cloud_sdk/dms/user-guide.md b/src/sap_cloud_sdk/dms/user-guide.md index 4952e2ca..7d2969f4 100644 --- a/src/sap_cloud_sdk/dms/user-guide.md +++ b/src/sap_cloud_sdk/dms/user-guide.md @@ -472,15 +472,6 @@ while True: - **Supported:** Yes - **Authentication:** XSUAA - **How to use:** Pass an optional `tenant` parameter to any operation. The SDK resolves a subscriber-scoped XSUAA token by replacing the provider's `identityzone` subdomain in the token URL. Omitting `tenant` uses the provider token (default). - - ```python - # Provider context (default) - repos = client.get_all_repositories() - - # Subscriber context - repos = client.get_all_repositories(tenant="subscriber-subdomain") - ``` - - **Further reading:** - [Multitenancy with SAP Document Management Service — SAP Help Portal](https://help.sap.com/docs/DOCUMENT_MANAGEMENT/f6e70dd4bffa4b65965b43feed4c9429/0f6dd1bbaca342ee9177b9ece3fcaaa3.html)