Skip to content

chore(appsec): keep Datadog block-path tags when OTel semantics are off - #20690

Draft
Hanane-Laassilia wants to merge 9 commits into
maximo/otel-http-server-semanticsfrom
hanane/otel-http-server-semantic
Draft

Hanane-Laassilia wants to merge 9 commits into
maximo/otel-http-server-semanticsfrom
hanane/otel-http-server-semantic

Conversation

@Hanane-Laassilia

@Hanane-Laassilia Hanane-Laassilia commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

The Flask (two paths) and FastAPI AppSec block paths were changed to call set_http_meta(..., request_headers=...) unconditionally. With OTel semantics off that adds tags the previous code never wrote (http.url without a query string, resolved client IP, referrer and header-tracing tags) and dispatches the AppSec and AI Guard listeners a second time, which broke three Flask AppSec snapshots (test_flask_ipblock_match_403, test_flask_ipblock_match_403_json, test_flask_userblock_match_403_json).

These paths now use http_block_metadata and set_url_tags_server, the helpers the Django block path already uses. Both check the semantics flag themselves, so output with the flag off matches main, and with the flag on the OTel keys are written.

Also on this branch: the aiohttp per-app trace_query_string override is honored when tagging url.query with OTel semantics (b5a0bab8bd).

Also adds the server OTLP snapshots: tests/integration/test_otel_http_server_snapshots.py builds server spans in a ddtrace-run subprocess and fills them through OTelHTTPSpanAttributes, without a web framework. Cases: route-based name with raw path, obfuscated query, user agent and both client addresses; unmatched route; unknown method; 302/404/500; DD_TRACE_HTTP_SERVER_ERROR_STATUSES=404-412; query-string tagging disabled (with and without a URL); and an exception's type winning in error.type. This completes Emmett's request on #20008 to turn the OTelHTTPSpanAttributes tests into snapshots, so four unit tests the snapshots now cover are removed from tests/tracer/test_resource.py.

Tracking: APMAPI-2036.

Testing

  • New tests/appsec/appsec/test_block_span_tags.py calls all three block functions with the flag off and on, and asserts the Datadog or OTel tags and that client IP, referrer and network.client.ip are never set by these paths.
  • The three Flask AppSec snapshots above are unchanged and pass again.
  • tests/integration/test_otel_http_server_snapshots.py and the client snapshots from chore(tracing): add HTTP client error statuses and OTel client snapshots #20689: 15 passed. The snapshots were reviewed against the RFC (server errors from 500, client errors from 400, integer status code and port, HTTP for _OTHER, method-only name without a route, no url.query when tagging is off).
  • At this branch's head: tracer OTel naming tests 87 passed, Flask 70 passed, AI Guard 24 passed; scripts/lint suitespec-check, style and typing passed.

Risks

Only block-path span tags change. If reading the request fails, the span keeps the status only, the same as the Django block path.

Additional Notes

Stacked directly on maximo/otel-http-server-semantics. The OTLP-shaped snapshots cannot be produced by the test agent's own snapshot endpoint, which is why the harness exists.

@Hanane-Laassilia Hanane-Laassilia added the changelog/no-changelog A changelog entry is not required for this PR. label Sep 30, 2026
@cit-pr-commenter-54b7da

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 1 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against maximo/otel-http-server-semantics using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

ddtrace/_trace/otel/http/tags.py                                        @DataDog/apm-sdk-capabilities-python
ddtrace/_trace/subscribers/web_framework.py                             @DataDog/apm-sdk-capabilities-python @DataDog/python-guild @DataDog/apm-idm-python
ddtrace/appsec/_contrib/fastapi/__init__.py                             @DataDog/asm-python
ddtrace/appsec/_contrib/flask/__init__.py                               @DataDog/asm-python
ddtrace/contrib/internal/trace_utils.py                                 @DataDog/python-guild @DataDog/apm-idm-python
ddtrace/contrib/internal/trace_utils_base.py                            @DataDog/python-guild @DataDog/apm-idm-python
tests/appsec/appsec/test_block_span_tags.py                             @DataDog/asm-python
tests/contrib/aiohttp/test_middleware.py                                @DataDog/python-guild @DataDog/apm-idm-python
tests/integration/test_otel_http_server_snapshots.py                    @DataDog/apm-core-python
tests/opentelemetry/test_otel_http_naming.py                            @DataDog/apm-sdk-capabilities-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_custom_server_error_statuses[false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_custom_server_error_statuses[true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_exception_error_type[false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_exception_error_type[true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_query_string_tagging_disabled[false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_query_string_tagging_disabled[true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_request[false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_request[true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_status[302-false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_status[302-true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_status[404-false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_status[404-true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_status[500-false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_status[500-true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_unknown_method[false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_unknown_method[true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_unmatched_route[false]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python
tests/snapshots/tests.integration.test_otel_http_server_snapshots.test_server_unmatched_route[true]_otlp_traces.json  @DataDog/apm-core-python @DataDog/apm-python

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 201 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 201 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=132)
ddtrace.llmobs._integrations.llama_index -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=130)
ddtrace.llmobs._integrations.base -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=130)
ddtrace.internal.opentelemetry.trace -×-> ddtrace.trace  (product:opentelemetry -> product:tracing, score=130)
ddtrace.llmobs._integrations.vllm -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=130)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Pipelines  Tests

✨ Unblock PR with BitsAI

❌ Errors

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 23 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | contrib/falcon 1/3 — ❌ 2 tests failed · 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

❌ MiddlewareTestCase::test_otel_semantics_refines_resource_from_route[py3.9] from test_middleware.py
module 'ddtrace.ext.http' has no attribute 'OTEL_REQUEST_METHOD'
❌ MiddlewareTestCase::test_request_metadata_available_during_resource_execution[py3.9] from test_middleware.py
assert {'method': None, 'resource': 'GET tests.contrib.falcon.test_suite.Resource', 'url': None} == {'method': 'GET', 'resource': 'GET tests.contrib.falcon.test_suite.Resource', 'url': 'http://falconframework.org/inspect'}
DataDog/apm-reliability/dd-trace-py | contrib/falcon 2/3 — ❌ 2 tests failed · 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

❌ MiddlewareTestCase::test_otel_semantics_refines_resource_from_route[py3.10] from test_middleware.py
module 'ddtrace.ext.http' has no attribute 'OTEL_REQUEST_METHOD'
❌ MiddlewareTestCase::test_request_metadata_available_during_resource_execution[py3.10] from test_middleware.py
assert {'resource': 'GET tests.contrib.falcon.test_suite.Resource', 'method': None, 'url': None} == {'resource': 'GET tests.contrib.falcon.test_suite.Resource', 'method': 'GET', 'url': 'http://falconframework.org/inspect'}
DataDog/apm-reliability/dd-trace-py | contrib/falcon 3/3 — ❌ 2 tests failed · 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

❌ MiddlewareTestCase::test_otel_semantics_refines_resource_from_route[py3.11] from test_middleware.py
module 'ddtrace.ext.http' has no attribute 'OTEL_REQUEST_METHOD'
❌ MiddlewareTestCase::test_request_metadata_available_during_resource_execution[py3.11] from test_middleware.py
assert {'resource': 'GET tests.contrib.falcon.test_suite.Resource', 'method': None, 'url': None} == {'resource': 'GET tests.contrib.falcon.test_suite.Resource', 'method': 'GET', 'url': 'http://falconframework.org/inspect'}

View all 23 failed jobs.

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🔄 Datadog retried 24 tests - 0 passed on retry View in Datadog

🚧 1 test that failed was ignored due to quarantine View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: b21897f | Docs | View more details | Give us feedback!

@Hanane-Laassilia
Hanane-Laassilia added this pull request to stack #20691 September 30, 2026 15:23
@pr-commenter

pr-commenter Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-10-08 16:22:37

Comparing candidate commit b21897f in PR branch hanane/otel-http-server-semantic with baseline commit 96a39b1 in branch maximo/otel-http-server-semantics.

📊 Benchmarking dashboard

Found 0 performance improvements and 4 performance regressions! Performance is the same for 441 metrics, 9 unstable metrics, 5 known flaky benchmarks, 5 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:httppropagationextract-empty_headers

  • 🟥 execution_time [+97.254ns; +122.471ns] or [+13.192%; +16.612%]

scenario:iastaspectsremodule-re_expand_aspect

  • 🟥 execution_time [+71.488µs; +81.062µs] or [+11.448%; +12.981%]

scenario:msgpackencoderscenario-simple_one_span

  • 🟥 execution_time [+482.305ns; +546.472ns] or [+11.873%; +13.452%]

scenario:span-start-finish-telemetry

  • 🟥 execution_time [+3.897ms; +4.074ms] or [+9.420%; +9.848%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-735.236ns; +739.777ns] or [-7.023%; +7.066%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-40.413ns; +37.990ns] or [-6.076%; +5.712%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-1785.775ns; +2020.873ns] or [-9.014%; +10.200%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1576.556ns; +2047.123ns] or [-8.349%; +10.842%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-371.129ns; +385.542ns] or [-8.720%; +9.059%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-203.322ns; +249.746ns] or [-7.635%; +9.378%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-120.739ns; +63.876ns] or [-8.786%; +4.648%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-4346.563ns; +4806.909ns] or [-9.115%; +10.080%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-907.460ns; +969.294ns] or [-8.999%; +9.613%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:httppropagationinject-ids_only

  • 🟥 execution_time [+2.880µs; +3.018µs] or [+20.123%; +21.085%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+172.196µs; +180.646µs] or [+46.844%; +49.143%]

scenario:iastaspectssplit-rsplit_aspect

  • 🟥 execution_time [+23.057µs; +27.310µs] or [+15.161%; +17.958%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+269.105ns; +306.339ns] or [+13.915%; +15.840%]

scenario:tracer-small

  • 🟥 execution_time [+38.979µs; +40.120µs] or [+15.557%; +16.012%]

Known flaky benchmarks without significant changes:

  • scenario:errortrackingflasksqli-baseline
  • scenario:flasksimple-iast-get
  • scenario:sethttpmeta-all-enabled
  • scenario:span-start
  • scenario:telemetryaddmetric-record-100-metrics

@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from ddfc698 to e93e5b2 Compare October 1, 2026 12:57
@Hanane-Laassilia Hanane-Laassilia changed the title fix(appsec): keep Datadog block-path tags when OTel semantics are off chore(appsec): keep Datadog block-path tags when OTel semantics are off Oct 1, 2026
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch 2 times, most recently from 41d3958 to 8228fd8 Compare October 5, 2026 11:47
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from 8228fd8 to 3c280da Compare October 5, 2026 13:14
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from 3c280da to 1e27692 Compare October 5, 2026 13:28
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from 1e27692 to a7874e5 Compare October 5, 2026 13:35
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from a7874e5 to 21f9961 Compare October 5, 2026 13:41
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch 2 times, most recently from fc38159 to 3dae795 Compare October 6, 2026 13:27
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch 2 times, most recently from cfb8806 to 9e2d313 Compare October 6, 2026 15:09
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from 9e2d313 to 0ed24f8 Compare October 6, 2026 15:35
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from 0ed24f8 to 03cfcaa Compare October 8, 2026 07:33
@Hanane-Laassilia
Hanane-Laassilia force-pushed the hanane/otel-http-server-semantic branch from 03cfcaa to b21897f Compare October 8, 2026 15:46
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Oct 9, 2026
## Description

Adds the shared foundation for emitting OpenTelemetry HTTP semantic conventions when `DD_TRACE_OTEL_SEMANTICS_ENABLED=true`.

This PR does **not** change HTTP integrations yet. The client and server integrations start using this foundation in #20009 and #20010.

This is the first part of the implementation for #19501.

### Configuration

`DD_TRACE_OTEL_SEMANTICS_ENABLED` remains disabled by default. When enabled, it enforces the settings required by the OTel HTTP semantics path:

- OTLP trace export
- Span attribute schema `v0`
- Peer-service defaults disabled
- Native span events enabled for OTLP export

Conflicting settings are overridden and reported through logs and telemetry.

### Shared HTTP semantics

The implementation lives under `ddtrace/_trace/otel/http/`:

- `tags.py` handles HTTP method normalization, URL attributes, server address and port extraction, query-string handling, status codes, and client/server error classification.
- `resource.py` generates low-cardinality HTTP resource names and prevents later instrumentation updates from overwriting resources set by user code.

`OTelHTTPSpanAttributes` is shared by the upcoming client and server layers. Those layers will instantiate it from `set_http_meta()` and provide metadata as it becomes available during the request lifecycle.

For example:

```python
attributes = OTelHTTPSpanAttributes(span, integration_config)
attributes.set_method("get")
attributes.set_url("https://example.com/users/42")
attributes.set_status_code(200)
attributes.set_resource("/users/{id}")
```

This produces:

```text
http.request.method = "GET"
http.request.method_original = "get"
url.scheme = "https"
url.path = "/users/42"
http.response.status_code = 200
resource = "GET /users/{id}"
```

Recreating the helper during later metadata phases is supported: method, resource, and status-error ownership are stored on the span.

### OTel requirements covered

The behavior follows the [OpenTelemetry HTTP span conventions](https://opentelemetry.io/docs/specs/semconv/http/http-spans/).

| Scenario | OTel behavior | Covered behavior |
| --- | --- | --- |
| Method `get` | Normalize the method and preserve its original spelling | Emits `http.request.method="GET"` and `http.request.method_original="get"` |
| Unknown method `PROPFIND` | Emit `_OTHER` and retain the original method | Emits `_OTHER`, preserves `PROPFIND`, and uses `HTTP` for resource naming |
| Server route `/users/{id}` | Use the low-cardinality route template instead of the raw request path | Refines the resource to `GET /users/{id}` |
| Client URL containing credentials | Redact user information from `url.full` | Emits `https://REDACTED:REDACTED@example.com/search` |
| Client 4xx response | Mark the span as an error | Sets `span.error=1` and a string status-derived `error.type` |
| Server 4xx response | Do not mark the span as an error by default | Leaves `span.error=0` |
| Server 5xx response | Mark the span as an error | Sets `span.error=1` |

Existing Datadog query capture, query obfuscation, and custom server error-range settings remain supported. Exception information takes precedence over status-derived error metadata.

### Resource ownership

Resource naming distinguishes instrumentation-generated values from user replacements:

1. Instrumentation may progressively refine a resource as more metadata becomes available.
2. If user code replaces that resource, subsequent instrumentation updates preserve the user value.
3. Raw request paths are never used as OTel server resources; route templates are used when available.

WebSocket resources are left unchanged to avoid collapsing them to the unknown-method resource name.

## Testing

Unit coverage includes:

- Method normalization and stale `method_original` cleanup
- Client and server URL attributes
- Credential redaction and query-string obfuscation
- Malformed URLs
- Client/server status-code error classification
- Repeated metadata updates
- Exception preservation
- User-owned resource preservation
- Schema and peer-service overrides
- OTLP writer selection and native span events

End-to-end OTLP snapshots are added later in the stack because they depend on the harness introduced in #20688:

- #20689: HTTP client snapshots
- #20690: HTTP server and AppSec snapshots

## Risks

The feature remains opt-in. This PR configures the enabled mode and adds the shared implementation, but it does not change emitted HTTP integration spans until the client and server follow-up PRs are merged.

## PR stack

1. #20008 — shared configuration and HTTP semantics foundation
2. #20688 — OTLP snapshot harness
3. #20009 — HTTP client integration
4. #20689 — client configuration and snapshots
5. #20010 — HTTP server/framework and AppSec integration
6. #20690 — AppSec block-path attributes and server snapshots

Co-authored-by: Hanane-Laassilia <hanane.laassilia@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/no-changelog A changelog entry is not required for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant