Skip to content

feat(cbc): add CBCEventHandler and feature status API - #422

Open
soumyadey wants to merge 2 commits into
SAP:mainfrom
soumyadey:feat/cbc-event-handler
Open

soumyadey wants to merge 2 commits into
SAP:mainfrom
soumyadey:feat/cbc-event-handler

Conversation

@soumyadey

@soumyadey soumyadey commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Description

Extends sap_cloud_sdk.cbc with two features requested in #412:

1. Feature status pull API on CBCClient — get_feature_statuses() returns a FeatureStatuses object listing all AI feature activation states for the current app tenant; get_feature_status(feature_id) queries a single feature server-side and returns FeatureStatus | None.

2. CBCEventHandler — parses incoming CBC CloudEvents (using the cloudevents SDK v2.x) from a raw HTTP request and dispatches to registered callbacks. Supports three CBC event types: cbc.notification.consumptionversion.created, cbc.notification.consumptionversion.updated, and cbc.notification.feature.activation.v1. Raises CBCEventError (subclass of CBCError) on malformed envelopes or structurally invalid event data; unknown event types are silently ignored.

New public symbols: CBCEventHandler, ConsumptionVersionEvent, FeatureActivationEvent, FeatureActivationData, CBCEventError — all exported from sap_cloud_sdk.cbc.

Related Issue

Closes #412

Type of Change

  • New feature (non-breaking change that adds functionality)

How to Test

Unit tests (no external service required):

pytest tests/cbc/unit/

Expected result: 101 unit tests pass.

Integration tests (real BTP service, default provisioning path):

8 passed in 41.93s
platform darwin -- Python 3.14.5, pytest-9.1.1

tests/cbc/integration/test_e2e_bdd.py::test_consumption_versions_non_empty PASSED
tests/cbc/integration/test_e2e_bdd.py::test_latest_version_non_empty PASSED
tests/cbc/integration/test_e2e_bdd.py::test_get_configuration_returns_configuration_data PASSED
tests/cbc/integration/test_e2e_bdd.py::test_configuration_has_configuration_objects PASSED
tests/cbc/integration/test_e2e_bdd.py::test_every_entity_has_id_and_data PASSED
tests/cbc/integration/test_e2e_bdd.py::test_get_entity_data_returns_entity_data PASSED
tests/cbc/integration/test_e2e_bdd.py::test_get_feature_statuses_returns_feature_statuses PASSED
tests/cbc/integration/test_e2e_bdd.py::test_get_feature_status_matches_full_list PASSED

8 passed, 1 warning in 41.93s

Checklist

  • I have read the Contributing Guidelines
  • I have verified that my changes solve the issue
  • I have added/updated automated tests to cover my changes
  • All tests pass locally
  • I have verified that my code follows the Code Guidelines
  • I have updated documentation (if applicable)
  • I have added type hints for all public APIs
  • My code does not contain sensitive information (credentials, tokens, etc.)
  • I have followed Conventional Commits for commit messages
  • (New modules only) I have attached e2e evidence showing the module working against a real BTP service
  • (Security-sensitive changes only) I have run /security-review and addressed all ❌ findings

Breaking Changes

None. Additive only — no existing public API is changed.

Additional Notes

Key design decisions:

  • Official CNCF CloudEvents SDK v2.x. CBCEventHandler uses cloudevents>=2.2.0 (cloudevents.core.*), the ground-up 2.x rewrite of the official CNCF Python SDK, rather than the 1.x compat layer (cloudevents.v1.*). The 2.x deserialization API — from_http_event(HTTPMessage(headers, body)) — auto-detects binary vs. structured CloudEvent mode and returns an immutable BaseCloudEvent with explicit getter methods (get_type(), get_data()), so the handler accepts both transport modes without any branching.
  • Single callback per event category. on_consumption_version_event and on_feature_activation_event each register one callback, replacing any previously registered one. This keeps the dispatch contract simple and predictable; fan-out is the application's responsibility if needed. If no callback is registered for a received event type, the event is silently dropped.
  • Separate event models, not reused from the pull API. ConsumptionVersionEvent, FeatureActivationEvent, and FeatureActivationData are dedicated plain Python classes. Reusing FeatureStatus from the pull API would couple two independently evolving wire formats. Internal wire-format models (_TenantContext, _ConsumptionVersionPayload, _FeatureActivationItem) are Pydantic frozen models with camelCase aliases — same pattern as the rest of _models.py.
  • get_feature_statuses() and get_feature_status(feature_id) mirror get_configuration / get_entity_data in structure — same error handling, same provisioning path.

AI assistance disclosure: This PR was developed with the assistance of Claude (Anthropic). See the SAP AI contribution guideline.

Add FeatureStatus and FeatureStatuses models, two new client methods
(get_feature_statuses, get_feature_status), matching unit tests, BDD
integration scenarios, and user-guide coverage.
…vents

Introduces CBCEventHandler that parses incoming CBC CloudEvents (using the
cloudevents SDK v2.x) and dispatches them to registered callbacks. Adds
ConsumptionVersionEvent and FeatureActivationEvent models, CBCEventError
exception, and documents event handling in the user guide.
@soumyadey

Copy link
Copy Markdown
Member Author

Self-review using the repo's /review-pr and /security-review skills. Both pass clean.

PR Review

PR Review: #422: feat(cbc): add CBCEventHandler and feature status API

Author: Soumya Dey Branch: feat/cbc-event-handler → main
Verdict: ✅ Ready to Merge
Summary: Solid, well-structured addition that follows all module patterns faithfully; all six new public symbols exported, telemetry complete, documentation thorough, tests comprehensive.


A: Process & Compliance

# Criterion Status Finding
A1 PR template complete ✅ How to Test filled (101 unit tests pass). Security-sensitive conditional ticked — /security-review run with no blocking findings.
A2 Conventional Commits ✅ Both commits follow feat(cbc): … correctly.
A3 Issue linked ✅ Closes #412 present.
A4 AI-generated code disclosure ✅ One-liner disclosure in Additional Notes references the SAP AI contribution guideline.

B: Security & Sensitive Data

# Criterion Status Finding
B1 No sensitive data in code ✅ No credentials, internal URLs, or tenant IDs as literals.
B2 No sensitive data in PR body ✅ Clean.

C: Code Quality

# Criterion Status Finding
C1 CI checks passing ⚠️ All jobs pass (Code Quality Checks, Tests & Coverage, Build & Package, Commit Validation, Check Version Bump, Analyze (python), CodeQL, REUSE Compliance Check, license/cla). Integration Tests skipped (scope detection). SDK review label present failing — PR needs an sdk-review: label applied.
C2 Version bump ✅ pyproject.toml version unchanged.
C3 Type hints ✅ All new public methods fully annotated.
C4 No hardcoded values ✅ Event type strings in module-level constants _CV_CREATED / _CV_UPDATED / _FA_ACTIVATION. feature_id URL-encoded via _url_quote(feature_id, safe='').
C5 Import organization ✅ All top-level, no lazy imports.
C6 Naming conventions ✅ Public PascalCase, private underscore-prefixed, file _events.py.
C7 No unused code ✅ ConsumptionVersionCallback / FeatureActivationCallback aliases both used.
C8 No unjustified new dependencies ✅ cloudevents>=2.2.0,<3 justified — official CNCF SDK for CloudEvent parsing.
C9 Proto code freshness ➖ No .proto changes.

D: API & Design

# Criterion Status Finding
D1 API future-proofing ✅ No config needed at construction. Operation enum extended correctly with CBC_HANDLE_EVENT.
D2 Public API hygiene ✅ FeatureActivationData imported from _models and exported via __init__.py / __all__. All internal wire-format models (_TenantContext, _ConsumptionVersionPayload, _FeatureActivationItem) correctly private and unexported.
D3 Breaking changes marked ✅ Additive only.
D4 Pagination & tenant filtering ✅ ?appTenantId= consistent with existing methods; no pagination needed.
D5 Telemetry instrumentation ✅ All three new operations decorated; CBC_HANDLE_EVENT added to Operation enum; count test updated to 262.
D6 Multi-tenancy support ✅ user-guide.md ## Multi-tenancy section present with all four required fields. app_tenant_id is first-class on both event types.

E: Tests & Documentation

# Criterion Status Finding
E1 Tests added/updated ✅ test_events.py (new, 16 tests), test_client.py and test_models.py updated. Binary + structured CloudEvent modes tested. All error paths covered.
E2 Documentation quality ✅ on_consumption_version_event / on_feature_activation_event have full Args: callback: docstrings. mTLS webhook security section documents full Subject+Issuer DN.
E2a Multi-tenancy documentation ✅ ## Multi-tenancy section with all four required fields.
E3 Module structure compliance ✅ _events.py follows underscore convention. Package structure maintained.

✅ Things Done Well

  • Telemetry complete on all three new operations; count test updated.
  • Internal wire-format models correctly private and unexported.
  • Both binary and structured CloudEvent modes tested.
  • Multi-tenancy section thorough with all four required fields.
  • Error paths comprehensively tested.
  • No version bump.
  • _FrozenModel pattern followed consistently for wire types.
  • URL-encoding applied to feature_id in get_feature_status query string.
  • Webhook security note documents full Subject+Issuer DN, stable across all CBC regions.
Security Review

Security Review: #422: feat(cbc): add CBCEventHandler and feature status API

Author: Soumya Dey Branch: feat/cbc-event-handler → main
Verdict: ✅ No security issues


# Criterion Status Finding
S1 Tenant subdomain validation ➖ N/A No new tenant_subdomain params; app_tenant_id has existing non-empty guard in _resolve_app_tenant_id().
S2 URL construction ✅ get_feature_status URL-encodes feature_id via _url_quote(feature_id, safe='') from urllib.parse.quote before inserting into query string.
S3 Credential emptiness checks ✅ No new credential fields introduced.
S4 JWT tokens verified ✅ handle_request parses raw HTTP requests as CBC CloudEvents. User guide documents that the webhook endpoint must verify the CBC mTLS client certificate Subject+Issuer DN before invoking handle_request — CBC delivers events over mTLS with a client certificate whose identity is stable across all CBC regions. CBCEventHandler itself correctly operates on the already-accepted HTTP request.
S5 No hardcoded credentials ✅ No hardcoded secrets, tokens, or API keys. Test fixtures use obviously fake values.
S6 Sensitive data not logged ✅ Only logger.debug("CBC %s %s", method, url) and TLS-retry info log — no tokens or secrets logged.
S7 No path traversal ➖ N/A No file path operations in the diff.
S8 HTTP base URLs validated ✅ Feature-status URLs built from self._base_url() resolving through the Destination Service fragment — same trusted source as all other CBC client methods.
S9 Multi-tenant cache isolation ➖ N/A No caches introduced. app_tenant_id is a first-class field on both event types, giving callbacks the context needed to enforce tenant isolation.
S10 CVE check for new deps ✅ cloudevents==2.2.0 (pinned in uv.lock); no known CVEs. Transitive deps deprecation==2.1.0 and python-dateutil are stable with no active CVEs.
S11 No verify=False ✅ _build_http_client() sets verify=ctx if ctx is not None else True — TLS verification always on.
S12 Audit log for mutations ➖ N/A All new operations are read-only GETs or inbound event parsing; no mutations on behalf of a tenant.

✅ Things Done Well

  • Strict Pydantic validation on all event payload fields with typed CBCEventError on failure — no silent data corruption.
  • TLS always on: _build_http_client() never passes verify=False.
  • app_tenant_id carried as first-class field on both event types.
  • All new HTTP operations are read-only GETs with no side effects requiring audit logging.
  • No credentials or secrets in any new code, including test fixtures.
  • feature_id URL-encoded before use in query string — no injection vector.
  • Webhook security guidance documents full Subject+Issuer DN pair (CN alone is insufficient), confirmed stable across all CBC regions via ADR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: CBC event handling - CloudEvent parsing, dispatch, and feature status API

1 participant