diff --git a/packages/aws-durable-execution-conformance-tests-otel/CONTRIBUTING.md b/packages/aws-durable-execution-conformance-tests-otel/CONTRIBUTING.md index ca7780e5..43194179 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/CONTRIBUTING.md +++ b/packages/aws-durable-execution-conformance-tests-otel/CONTRIBUTING.md @@ -69,6 +69,7 @@ The currently supported telemetry assertions are: | `minimum_spans` | Minimum number of normalized spans; defaults to `1`. | | `minimum_invocations` | Minimum canonical durable invocation span occurrences; identical spans are counted separately. Defaults to `1`. | | `require_execution_correlation` | Require the durable execution ARN on the trace; defaults to `true`. | +| `require_unique_root_per_trace` | Reject any trace ID containing more than one distinct parentless span. Duplicate exports with the same span ID count once. | | `require_all_spans` | Require every normalized span to match at least one span assertion. | | `span_assertion_scope` | Limit complete span coverage to spans matching this partial selector. | | `exact_attribute_prefixes` | Require assertions to enumerate every attribute under the listed prefixes. | @@ -165,6 +166,13 @@ Keep `ExpectedExecutionHistory` and `ExpectedResult` focused on the execution behavior needed to produce the telemetry. Keep `TelemetryAssertions` portable across the complete exporter/backend support matrix. +Set `require_unique_root_per_trace: true` for trace-view requirements. A +backend may return a correlated subset without the upstream root, so the +assertion permits zero observed roots for a trace ID. It rejects two or more +distinct parentless spans because that creates a disconnected trace forest and +makes backend-derived entry point, duration, critical path, and sampling +behavior ambiguous. + The catalog uses separate requirements when two public plugins intentionally produce different trace views. Invocation-view cases assert per-invocation operation hierarchy. In execution-view cases, `Invocation` spans remain diff --git a/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/backends/xray.py b/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/backends/xray.py index d8a8bdad..0ce45c0e 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/backends/xray.py +++ b/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/backends/xray.py @@ -32,6 +32,8 @@ PollingBackend, ) +_WORKFLOW_SERVICE_NAME = "Workflow" + def _metadata_attributes(metadata: Any) -> dict[str, Any]: if not isinstance(metadata, Mapping): @@ -119,10 +121,13 @@ def _lookup(self, query: TelemetryQuery) -> Trace | None: if query.trace_id: trace_ids = [query.trace_id] else: + # ADOT names a parentless INTERNAL root segment after its span, + # so the durable Workflow trace is indexed separately from the + # ambient trace that uses the configured service name. summary_request = { "StartTime": query.started_at, "EndTime": query.ended_at, - "FilterExpression": f'service("{query.service_name}")', + "FilterExpression": (f'service("{query.service_name}") OR service("{_WORKFLOW_SERVICE_NAME}")'), } while True: response = self._client.get_trace_summaries(**summary_request) diff --git a/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/validators.py b/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/validators.py index 996be2ed..f225d3bc 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/validators.py +++ b/packages/aws-durable-execution-conformance-tests-otel/src/aws_durable_execution_conformance_tests_otel/validators.py @@ -54,6 +54,25 @@ def _invocation_count(trace: Trace) -> int: ) +def _unique_root_errors(trace: Trace) -> list[str]: + roots_by_trace: dict[str, dict[str, Span]] = {} + for span in trace.spans: + if span.parent_span_id is None: + roots_by_trace.setdefault(span.trace_id, {}).setdefault(span.span_id, span) + + errors: list[str] = [] + for trace_id, roots_by_id in sorted(roots_by_trace.items()): + if len(roots_by_id) <= 1: + continue + roots = sorted( + roots_by_id.values(), + key=lambda span: (span.start_time, span.span_id), + ) + root_list = ", ".join(f"{span.name!r} ({span.span_id})" for span in roots) + errors.append(f"Trace {trace_id} contains {len(roots)} distinct root spans: {root_list}") + return errors + + def _is_sequence(value: Any) -> bool: return isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)) @@ -635,6 +654,12 @@ def validate_trace( if len(trace.spans) < minimum_spans: errors.append(f"Expected at least {minimum_spans} span(s), found {len(trace.spans)}") + require_unique_root = assertions.get("require_unique_root_per_trace", False) + if not isinstance(require_unique_root, bool): + errors.append("require_unique_root_per_trace must be a boolean") + elif require_unique_root: + errors.extend(_unique_root_errors(trace)) + timestamp_tolerance = _timestamp_tolerance(feature_disparities) for span in trace.spans: if span.start_time - span.end_time > timestamp_tolerance: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-1.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-1.yaml index b03c5a0f..4f075513 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-1.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-1.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-10.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-10.yaml index 72de78c4..69329e19 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-10.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-10.yaml @@ -43,6 +43,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 8 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 2 span_assertions: - select: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-11.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-11.yaml index 7704a220..9cc6afa0 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-11.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-11.yaml @@ -22,6 +22,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 6 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 3 allowed_execution_arns: - ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-12.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-12.yaml index 4e79085e..f9c4959d 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-12.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-12.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 3 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-13.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-13.yaml index a604be3d..d2911136 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-13.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-13.yaml @@ -32,6 +32,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-14.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-14.yaml index cbf1b88a..98dd1b85 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-14.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-14.yaml @@ -32,6 +32,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-15.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-15.yaml index 2dd1291f..f9789c65 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-15.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-15.yaml @@ -15,6 +15,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 1 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Invocation diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-16.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-16.yaml index 287eb4ef..cf72036c 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-16.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-16.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-17.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-17.yaml index 1224c7a9..b88cae39 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-17.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-17.yaml @@ -45,6 +45,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 8 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 2 span_assertions: - select: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-18.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-18.yaml index 96c972e9..0509218d 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-18.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-18.yaml @@ -22,6 +22,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 6 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 3 allowed_execution_arns: - ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-19.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-19.yaml index 1793e1af..7f749b44 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-19.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-19.yaml @@ -12,6 +12,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 1 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Invocation diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-2.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-2.yaml index e794bbae..1887f84c 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-2.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-2.yaml @@ -26,6 +26,7 @@ TelemetryAssertions: minimum_spans: 6 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-3.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-3.yaml index 0748eff1..81c1afcc 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-3.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-3.yaml @@ -31,6 +31,7 @@ TelemetryAssertions: minimum_spans: 6 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-4.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-4.yaml index c40ce06f..252f55f0 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-4.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-4.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-5.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-5.yaml index 9740e05d..01ec31ba 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-5.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-5.yaml @@ -27,6 +27,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 5 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-6.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-6.yaml index 10158170..2b3edff5 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-6.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-6.yaml @@ -39,6 +39,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 9 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-7.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-7.yaml index 6df6a65c..490c94e4 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-7.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-7.yaml @@ -39,6 +39,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 9 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-8.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-8.yaml index 6f10cf38..48150762 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-8.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-8.yaml @@ -30,6 +30,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 6 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-9.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-9.yaml index b80e7767..bc508c74 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-9.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-execution/otel-execution-9.yaml @@ -31,6 +31,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 6 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 2 span_assertions: - select: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-1.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-1.yaml index 38542a2b..2d580b16 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-1.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-1.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-10.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-10.yaml index c7ee53ab..f121dfb8 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-10.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-10.yaml @@ -44,6 +44,7 @@ TelemetryAssertions: minimum_spans: 10 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -144,9 +145,6 @@ TelemetryAssertions: status: OK service_name: ${SERVICE_NAME} links: - - name: otel-callback - attributes: - durable.operation.id: ${CALLBACK_CONTEXT} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -203,9 +201,6 @@ TelemetryAssertions: status: OK service_name: ${SERVICE_NAME} links: - - name: "${/^(?:otel-callback(?: create callback id|-callback)|CALLBACK)$/}" - attributes: - durable.operation.id: ${CALLBACK1} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-11.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-11.yaml index 50c35ba9..3c062203 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-11.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-11.yaml @@ -23,6 +23,7 @@ TelemetryAssertions: minimum_spans: 7 minimum_invocations: 3 require_execution_correlation: true + require_unique_root_per_trace: true allowed_execution_arns: - ${EXECUTION_ARN} - ${TARGET_EXECUTION_ARN} @@ -162,9 +163,6 @@ TelemetryAssertions: status: OK service_name: ${SERVICE_NAME} links: - - name: otel-invoke - attributes: - durable.operation.id: ${INVOKE1} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-12.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-12.yaml index 96f715fa..4404f15e 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-12.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-12.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 3 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-13.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-13.yaml index 885fb497..1f157a64 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-13.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-13.yaml @@ -32,6 +32,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-14.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-14.yaml index 12a617e8..1c6944d1 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-14.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-14.yaml @@ -32,6 +32,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-15.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-15.yaml index 978f2535..e0a9a64e 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-15.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-15.yaml @@ -15,6 +15,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-16.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-16.yaml index 56d96f17..ae071d46 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-16.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-16.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-17.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-17.yaml index b7a143a9..53568a6c 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-17.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-17.yaml @@ -46,6 +46,7 @@ TelemetryAssertions: minimum_spans: 10 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -144,9 +145,6 @@ TelemetryAssertions: status: ERROR service_name: ${SERVICE_NAME} links: - - name: otel-failed-callback - attributes: - durable.operation.id: ${FAILED_CALLBACK_CONTEXT} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -202,9 +200,6 @@ TelemetryAssertions: status: ERROR service_name: ${SERVICE_NAME} links: - - name: "${/^(?:otel-failed-callback(?: create callback id|-callback)|CALLBACK)$/}" - attributes: - durable.operation.id: ${FAILED_CALLBACK} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-18.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-18.yaml index bfc53b7e..5b8b8b37 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-18.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-18.yaml @@ -23,6 +23,7 @@ TelemetryAssertions: minimum_spans: 7 minimum_invocations: 3 require_execution_correlation: true + require_unique_root_per_trace: true allowed_execution_arns: - ${EXECUTION_ARN} - ${TARGET_EXECUTION_ARN} @@ -164,9 +165,6 @@ TelemetryAssertions: status: ERROR service_name: ${SERVICE_NAME} links: - - name: otel-failed-invoke - attributes: - durable.operation.id: ${FAILED_INVOKE} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-19.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-19.yaml index e0470817..bedc7238 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-19.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-19.yaml @@ -12,6 +12,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-2.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-2.yaml index cca93f93..018e21fa 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-2.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-2.yaml @@ -26,6 +26,7 @@ TelemetryAssertions: minimum_spans: 8 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -125,9 +126,6 @@ TelemetryAssertions: status: OK service_name: ${SERVICE_NAME} links: - - name: otel-wait - attributes: - durable.operation.id: ${WAIT1} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-3.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-3.yaml index 8b596da2..d9961bd6 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-3.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-3.yaml @@ -31,6 +31,7 @@ TelemetryAssertions: minimum_spans: 7 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -161,9 +162,6 @@ TelemetryAssertions: status: OK service_name: ${SERVICE_NAME} links: - - name: otel-retry - attributes: - durable.operation.id: ${RETRY_STEP} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-4.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-4.yaml index ae7668e5..c1aa23f9 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-4.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-4.yaml @@ -20,6 +20,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 4 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-5.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-5.yaml index fc9c9395..c1eed797 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-5.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-5.yaml @@ -27,6 +27,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 5 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-6.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-6.yaml index 648ba685..5abf65ed 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-6.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-6.yaml @@ -39,6 +39,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 9 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-7.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-7.yaml index d5fde323..2231485a 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-7.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-7.yaml @@ -39,6 +39,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 9 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-8.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-8.yaml index 89ecf4ee..f271a1fe 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-8.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-8.yaml @@ -30,6 +30,7 @@ ExpectedResult: TelemetryAssertions: minimum_spans: 6 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-9.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-9.yaml index 095935ee..051f7ba3 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-9.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-invocation/otel-invocation-9.yaml @@ -32,6 +32,7 @@ TelemetryAssertions: minimum_spans: 7 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -162,9 +163,6 @@ TelemetryAssertions: status: OK service_name: ${SERVICE_NAME} links: - - name: otel-condition - attributes: - durable.operation.id: ${CONDITION1} - name: Workflow attributes: durable.execution.arn: ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-1.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-1.yaml index 3d4227fb..4f1fcc41 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-1.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-1.yaml @@ -31,6 +31,7 @@ TelemetryAssertions: minimum_spans: 8 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -104,16 +105,9 @@ TelemetryAssertions: $any_of: [UNSET, OK] service_name: ${SERVICE_NAME} links: - $any_of: - - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} - - - name: otel-long-wait - attributes: - durable.operation.id: ${WAIT1} - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} + - name: Workflow + attributes: + durable.execution.arn: ${EXECUTION_ARN} kind: INTERNAL attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -189,6 +183,7 @@ ExecutionTelemetryAssertions: minimum_spans: 6 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-2.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-2.yaml index 9a4a6985..bd77267d 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-2.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-2.yaml @@ -35,6 +35,7 @@ TelemetryAssertions: minimum_spans: 7 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -108,16 +109,9 @@ TelemetryAssertions: $any_of: [UNSET, OK] service_name: ${SERVICE_NAME} links: - $any_of: - - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} - - - name: otel-long-retry - attributes: - durable.operation.id: ${RETRY_STEP} - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} + - name: Workflow + attributes: + durable.execution.arn: ${EXECUTION_ARN} kind: INTERNAL attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -194,6 +188,7 @@ ExecutionTelemetryAssertions: minimum_spans: 6 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true span_assertions: - select: name: Workflow diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-3.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-3.yaml index 1c4d8b0a..2c6c0cf2 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-3.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-3.yaml @@ -46,6 +46,7 @@ TelemetryAssertions: minimum_spans: 10 minimum_invocations: 2 require_execution_correlation: true + require_unique_root_per_trace: true require_all_spans: true span_assertion_scope: attributes: @@ -120,16 +121,9 @@ TelemetryAssertions: $any_of: [UNSET, OK] service_name: ${SERVICE_NAME} links: - $any_of: - - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} - - - name: otel-long-callback - attributes: - durable.operation.id: ${CALLBACK_CONTEXT} - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} + - name: Workflow + attributes: + durable.execution.arn: ${EXECUTION_ARN} kind: INTERNAL attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -154,16 +148,9 @@ TelemetryAssertions: $any_of: [UNSET, OK] service_name: ${SERVICE_NAME} links: - $any_of: - - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} - - - name: '${/^(?:otel-long-callback(?: create callback id|-callback)|CALLBACK)$/}' - attributes: - durable.operation.id: ${CALLBACK1} - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} + - name: Workflow + attributes: + durable.execution.arn: ${EXECUTION_ARN} kind: INTERNAL attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -236,6 +223,7 @@ TelemetryAssertions: ExecutionTelemetryAssertions: minimum_spans: 8 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 2 span_assertions: - select: diff --git a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-4.yaml b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-4.yaml index d023cadf..3a90fed4 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-4.yaml +++ b/packages/aws-durable-execution-conformance-tests-otel/test-requirements/otel-long-running/otel-long-running-4.yaml @@ -28,6 +28,7 @@ TelemetryAssertions: minimum_spans: 10 minimum_invocations: 4 require_execution_correlation: true + require_unique_root_per_trace: true allowed_execution_arns: ['${EXECUTION_ARN}', '${TARGET_EXECUTION_ARN}'] require_all_spans: true span_assertion_scope: @@ -168,16 +169,9 @@ TelemetryAssertions: $any_of: [UNSET, OK] service_name: ${SERVICE_NAME} links: - $any_of: - - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} - - - name: otel-long-invoke - attributes: - durable.operation.id: ${INVOKE1} - - name: Workflow - attributes: - durable.execution.arn: ${EXECUTION_ARN} + - name: Workflow + attributes: + durable.execution.arn: ${EXECUTION_ARN} kind: INTERNAL attributes: durable.execution.arn: ${EXECUTION_ARN} @@ -201,16 +195,9 @@ TelemetryAssertions: $any_of: [UNSET, OK] service_name: ${SERVICE_NAME} links: - $any_of: - - - name: Workflow - attributes: - durable.execution.arn: ${TARGET_EXECUTION_ARN} - - - name: otel-long-invoke-target-wait - attributes: - durable.operation.id: '*' - - name: Workflow - attributes: - durable.execution.arn: ${TARGET_EXECUTION_ARN} + - name: Workflow + attributes: + durable.execution.arn: ${TARGET_EXECUTION_ARN} kind: INTERNAL attributes: durable.execution.arn: ${TARGET_EXECUTION_ARN} @@ -227,6 +214,7 @@ TelemetryAssertions: ExecutionTelemetryAssertions: minimum_spans: 8 require_execution_correlation: true + require_unique_root_per_trace: true minimum_invocations: 4 allowed_execution_arns: - ${EXECUTION_ARN} diff --git a/packages/aws-durable-execution-conformance-tests-otel/tests/test_backends.py b/packages/aws-durable-execution-conformance-tests-otel/tests/test_backends.py index b6f450cc..1dfef324 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/tests/test_backends.py +++ b/packages/aws-durable-execution-conformance-tests-otel/tests/test_backends.py @@ -851,7 +851,7 @@ class _XRay: batch_get_calls = 0 def get_trace_summaries(self, **kwargs: Any) -> dict[str, Any]: - assert kwargs["FilterExpression"] == 'service("conformance")' + assert kwargs["FilterExpression"] == ('service("conformance") OR service("Workflow")') return {"TraceSummaries": [{"Id": "1-aaaaaaaa-bbbbbbbbbbbbbbbbbbbbbbbb"}]} def batch_get_traces(self, **kwargs: Any) -> dict[str, Any]: @@ -878,6 +878,84 @@ def batch_get_traces(self, **kwargs: Any) -> dict[str, Any]: assert trace.spans[0].attributes["durable.execution.arn"] == "arn:test" +def test_xray_correlates_workflow_and_ambient_service_traces() -> None: + ambient_trace_id = "1-aaaaaaaa-bbbbbbbbbbbbbbbbbbbbbbbb" + workflow_trace_id = "1-aaaaaaaa-cccccccccccccccccccccccc" + + def document( + trace_id: str, + span_id: str, + name: str, + *, + parent_span_id: str | None = None, + ) -> dict[str, Any]: + segment: dict[str, Any] = { + "trace_id": trace_id, + "id": span_id, + "name": name, + "start_time": 1, + "end_time": 2, + "metadata": {"durable.execution.arn": "arn:test"}, + } + if parent_span_id is not None: + segment["parent_id"] = parent_span_id + return {"Document": json.dumps(segment)} + + class _XRay: + def get_trace_summaries(self, **kwargs: Any) -> dict[str, Any]: + assert kwargs["FilterExpression"] == ('service("conformance") OR service("Workflow")') + return { + "TraceSummaries": [ + {"Id": ambient_trace_id}, + {"Id": workflow_trace_id}, + ] + } + + def batch_get_traces(self, **kwargs: Any) -> dict[str, Any]: + assert kwargs["TraceIds"] == [ambient_trace_id, workflow_trace_id] + return { + "Traces": [ + { + "Segments": [ + document( + ambient_trace_id, + "1" * 16, + "conformance", + ), + document( + ambient_trace_id, + "2" * 16, + "Invocation", + parent_span_id="1" * 16, + ), + ] + }, + { + "Segments": [ + document( + workflow_trace_id, + "3" * 16, + "Workflow", + ) + ] + }, + ] + } + + backend = XRayBackend(_XRay(), sleep=lambda _seconds: None) + trace = backend.find_trace( + _query(), + PollingPolicy(timeout_seconds=1, interval_seconds=0, max_attempts=1), + ) + + assert trace.trace_id == "a" * 8 + "c" * 24 + assert [(span.name, span.trace_id) for span in trace.spans] == [ + ("Workflow", "a" * 8 + "c" * 24), + ("conformance", "a" * 8 + "b" * 24), + ("Invocation", "a" * 8 + "b" * 24), + ] + + def test_xray_paginates_summaries_and_trace_batches() -> None: trace_ids = [f"1-aaaaaaaa-{index:024x}" for index in range(1, 7)] diff --git a/packages/aws-durable-execution-conformance-tests-otel/tests/test_resources.py b/packages/aws-durable-execution-conformance-tests-otel/tests/test_resources.py index f0373bb0..bd274b7c 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/tests/test_resources.py +++ b/packages/aws-durable-execution-conformance-tests-otel/tests/test_resources.py @@ -99,6 +99,26 @@ def test_requirement_service_names_use_configured_placeholder( assert service_name_assertions > 0 +@pytest.mark.parametrize( + "suite_name", + ["otel-invocation", "otel-execution", "otel-long-running"], +) +def test_requirement_catalog_rejects_multiple_roots_per_trace( + suite_name: str, +) -> None: + assertion_blocks = 0 + for requirement_path in _requirements(suite_name).values(): + requirement = load_yaml_file(requirement_path) + for assertion_key in ("TelemetryAssertions", "ExecutionTelemetryAssertions"): + assertions = requirement.get(assertion_key) + if not assertions: + continue + assertion_blocks += 1 + assert assertions["require_unique_root_per_trace"] is True + + assert assertion_blocks > 0 + + def test_long_running_catalog_uses_configurable_delays() -> None: requirements = _requirements("otel-long-running") second_invocation_descendants = { @@ -213,19 +233,15 @@ def test_long_running_catalog_uses_configurable_delays() -> None: if assertion["select"]["name"] in {"Invocation", "Workflow"}: assert assertion["expect"]["links"] == [] continue - links = assertion["expect"]["links"] - link_alternatives = links["$any_of"] if isinstance(links, dict) else [links] execution_arn = assertion["expect"]["attributes"]["durable.execution.arn"] - assert all( - link_set[-1] - == { + assert assertion["expect"]["links"] == [ + { "name": "Workflow", "attributes": { "durable.execution.arn": execution_arn, }, } - for link_set in link_alternatives - ) + ] @pytest.mark.parametrize( @@ -484,7 +500,18 @@ def test_invocation_view_catalog_exercises_span_hierarchy_assertions() -> None: assert linked_span["attributes"]["durable.operation.id"] if selected_name not in {"Invocation", "Workflow"} and case_number != 15: assert link_alternatives - assert all(link_set[-1]["name"] == "Workflow" for link_set in link_alternatives) + assert all( + link_set + == [ + { + "name": "Workflow", + "attributes": { + "durable.execution.arn": "${EXECUTION_ARN}", + }, + } + ] + for link_set in link_alternatives + ) assert expected["kind"] == "INTERNAL" assert "span.name" not in expected["attributes"] assert "span.kind" not in expected["attributes"] @@ -601,6 +628,7 @@ def test_execution_view_catalog_asserts_workflow_parentage_and_ambient_links() - ] assert assertions["require_execution_correlation"] is True + assert assertions["require_unique_root_per_trace"] is True assert "require_all_spans" not in assertions assert "exact_attribute_prefixes" not in assertions assert assertions["minimum_spans"] >= len(span_assertions) diff --git a/packages/aws-durable-execution-conformance-tests-otel/tests/test_validators.py b/packages/aws-durable-execution-conformance-tests-otel/tests/test_validators.py index 9735b134..2d43867a 100644 --- a/packages/aws-durable-execution-conformance-tests-otel/tests/test_validators.py +++ b/packages/aws-durable-execution-conformance-tests-otel/tests/test_validators.py @@ -105,6 +105,76 @@ def test_counts_case_insensitive_invocations_without_status_attributes() -> None ) +def test_requires_at_most_one_distinct_root_per_trace() -> None: + trace = _trace() + root, child = trace.spans + ambient_root = replace( + root, + trace_id="9" * 32, + span_id="4" * 16, + name="ambient", + ) + invocation = replace( + child, + trace_id=ambient_root.trace_id, + span_id="5" * 16, + parent_span_id=ambient_root.span_id, + name="Invocation", + ) + + assert ( + validate_trace( + replace(trace, spans=(root, child, ambient_root, invocation)), + {"require_unique_root_per_trace": True}, + _query(), + ) + == [] + ) + assert ( + validate_trace( + replace( + trace, + spans=( + replace( + root, + parent_span_id="f" * 16, + ), + ), + ), + {"require_unique_root_per_trace": True}, + _query(), + ) + == [] + ) + + +def test_reports_multiple_distinct_roots_and_ignores_duplicate_exports() -> None: + trace = _trace() + root, child = trace.spans + second_root = replace( + root, + span_id="4" * 16, + name="second-root", + ) + + assert ( + validate_trace( + replace(trace, spans=(root, root, child)), + {"require_unique_root_per_trace": True}, + _query(), + ) + == [] + ) + assert validate_trace( + replace(trace, spans=(root, child, second_root)), + {"require_unique_root_per_trace": True}, + _query(), + ) == [ + f"Trace {root.trace_id} contains 2 distinct root spans: " + f"'root' ({root.span_id}), 'second-root' ({second_root.span_id})" + ] + + def test_rejects_spans_that_end_before_they_start() -> None: trace = _trace() root, child = trace.spans @@ -1423,6 +1493,7 @@ def test_reports_invalid_span_assertion_schema() -> None: { "allowed_execution_arns": 1, "exact_attribute_prefixes": 1, + "require_unique_root_per_trace": "yes", "span_assertion_scope": ["plugin"], "span_assertions": { "select": {"name": "root"}, @@ -1433,6 +1504,7 @@ def test_reports_invalid_span_assertion_schema() -> None: _query(), ) assert count_errors == [ + "require_unique_root_per_trace must be a boolean", "allowed_execution_arns must be a string or sequence of strings", "exact_attribute_prefixes must be a string or sequence of strings", "span_assertion_scope must be a mapping or sequence of mappings",