Cloud Service Profile conformance

The Cloud Service Profile is Benzene's named conformance target: the operational requirements (R1–R8) a deployable service layers onto the core model. This port implements all eight. The table maps each requirement to the Python API that satisfies it and the test that proves it.

# Requirement Implementation Proof
R1 Hosted middleware pipeline behind a transport MiddlewarePipeline + message_router, hosted by BenzeneHttpApp, the gRPC binding, and the three cloud hosts every transport test suite
R2 Topics served through the handler registry Registry is the single source of truth; every host builds its app from it tests/test_core.py
R3 Health checks + HTTP /benzene/health HealthChecks + health_interception (reserved benzene:healthcheck); the HTTP surface returns the full {isHealthy, healthChecks} aggregate, 200/503 tests/test_wellknown.py, tests/test_cloud_wellknown.py
R4 Wire-envelope invocability + HTTP /benzene/invoke BenzeneMessageApplication.handle is the envelope entry point; StandardPaths exposes POST /benzene/invoke tests/test_wellknown.py, tests/test_cloud_wellknown.py
R5 Registry-derived spec at /benzene/spec ServiceSpec.derive(registry, service=...) + spec_interception (reserved benzene:spec); StandardPaths exposes GET /benzene/spec tests/test_wellknown.py, tests/test_cloud_wellknown.py
R6 Mesh service-side feeds ServiceDescriptor (reserved benzene:mesh), MeshFeedSender (register / heartbeat / traces), trace_middleware (one TraceEvent per invocation) tests/test_mesh.py, mesh-* conformance fixtures
R7 Default /benzene/ standard paths, configurable StandardPaths(prefix="/benzene", ...) — one config object, relocatable prefix; threaded into every HTTP-capable host tests/test_wellknown.py::test_prefix_is_configurable_and_relocates_every_surface
R8 Join + propagate W3C trace context trace_middleware joins an inbound traceparent; with_trace_propagation forwards the current span to outbound calls tests/test_mesh.py (outbound trace propagation), mesh-trace-cases

Claiming the profile

A service claims the profile by hosting its handlers behind a transport (R1/R2 — automatic) and wiring the operational surfaces. Over HTTP that is one StandardPaths plus the mesh feeds:

from benzene.core import HealthChecks, ServiceSpec
from benzene.http import BenzeneHttpApp, StandardPaths

health = HealthChecks().add("db", check_db)
spec = ServiceSpec.derive(registry, service="orders")

app = BenzeneHttpApp(
    router,
    application=application,
    standard_paths=StandardPaths(health=health, spec=spec),   # R3 + R4 + R5 + R7
)

The same standard_paths= argument works on GcpFunctionsApp, AwsLambdaApp, and AzureFunctionsApp, so a service claims the HTTP surfaces identically whether it runs under ASGI or a cloud function. Add trace_middleware and the MeshFeedSender for R6/R8 — see joining the mesh.

A service booted from a BenzeneStartUp declares the surfaces once, in the composition root, by returning them on its AppDefinition (alongside router and middleware) — then every host and the test harness pick them up from that one declaration:

class OrdersStartUp(BenzeneStartUp):
    def configure(self, services, config) -> AppDefinition:
        router, registry = ...   # built here, so the spec derives from the registry in hand
        return AppDefinition(
            router=router,
            standard_paths=StandardPaths(health=health, spec=ServiceSpec.derive(registry, service="orders")),
        )

The two documents: spec vs. descriptor

R5's spec document and R6's mesh ServiceDescriptor are both derived from the registry and share one schema derivation (benzene.core.json_schema), but serve different audiences:

A service can expose either or both; they never disagree about the topics, because both read the one registry.