Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7d89a4e
fix: preserve handler worker context
Oct 2, 2026
0ea7ea2
test: classify handler dispatch unit coverage
Oct 2, 2026
e9cda8c
ci: validate the stacked conformance branch
Oct 3, 2026
049099e
ci: pin revised OTel conformance requirements
Oct 3, 2026
95322ac
fix: bind missing handler trace context
zhongkechen Oct 3, 2026
55a7870
ci: pin complete OTel invocation assertions
Oct 3, 2026
812f112
fix: confine handler context to worker scopes
Oct 3, 2026
a4e5580
test: pin validated OTel matcher schemas
Oct 3, 2026
6cb3430
ci: validate OTel conformance against main
Oct 3, 2026
370295f
test: retain published-core context expectations
Oct 3, 2026
e1f2cc9
test: isolate the published-core OTel environment
Oct 3, 2026
441eae7
Merge commit 'e1f2cc9a479cb7a97aec53e5995359e351fd34ec' into test/ote…
Oct 3, 2026
bdb6aca
test: pin legacy OTel compatibility coverage
Oct 3, 2026
63cd43c
Merge commit 'bdb6aca293f5e77614400c2b620752cd2da9a082' into test/ote…
Oct 3, 2026
97f78ad
ci: verify minimum-core OTel compatibility
zhongkechen Oct 3, 2026
dd69e05
test: cover OTel handler context scopes directly
Oct 3, 2026
146f231
test: pin reviewed OTel conformance revisions
zhongkechen Oct 4, 2026
ed3c6ba
fix: preserve uninstrumented worker context
zhongkechen Oct 4, 2026
ef7c447
test: refresh OTel conformance requirement pin
zhongkechen Oct 6, 2026
9055bda
fix: bind execution view and stabilize CI
zhongkechen Oct 6, 2026
6a46556
Merge commit 'refs/maintenance-python-20261006/base' into maintenance…
Oct 6, 2026
da80964
Merge commit 'refs/maintenance-python-20261006/base' into maintenance…
Oct 6, 2026
3fcf377
ci: route OTel conformance to CodeBuild
Oct 7, 2026
680232b
ci: preserve queued conformance runs
Oct 7, 2026
3373a3f
ci: adopt shared backend queue preservation
Oct 7, 2026
a663841
fix: build new js example workspace dependencies
Oct 7, 2026
806e118
chore: merge main after telemetry view guard
Oct 7, 2026
746e388
fix: deliver external completion hooks once
Oct 8, 2026
12e760c
test: add external callback conformance
Oct 8, 2026
246cad8
fix: consume delivered callback update IDs
Oct 8, 2026
3bacb93
test: pin strict invocation parent conformance
Oct 8, 2026
22004eb
fix: align empty callback errors across stores
Oct 8, 2026
cd81a3d
fix: preserve empty callback error history payload
Oct 8, 2026
2a10053
fix(testing): preserve empty history payloads
Oct 8, 2026
a6c8c90
refactor: simplify optional handler context scopes
Oct 8, 2026
21be89e
fix(otel): unify invocation lifecycle on handler worker
Oct 8, 2026
d415329
fix: propagate plugin context into concurrent branches
Oct 9, 2026
192ce11
test: cover instrumented branch context handoff
Oct 9, 2026
eded23e
fix(otel): retain completions until their parent span exists
Oct 9, 2026
eb6c78f
test: align conformance runner reference assertion
Oct 9, 2026
d59af28
ci: use validated 26-case conformance runner
Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions .github/scripts/install_otel_test_wheels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Install and verify built artifacts without silently using editable sources."""

from __future__ import annotations

import argparse
import ast
import hashlib
import importlib.metadata
import json
import subprocess
import sys
import zipfile
from pathlib import Path


ROOT = Path(__file__).resolve().parents[2]
CORE = "aws-durable-execution-sdk-python"
OTEL = CORE + "-otel"
TESTING = CORE + "-testing"


def built_wheel(package: str) -> Path:
directory = ROOT / "packages" / package
module = package.replace("-", "_")
about = ast.parse((directory / "src" / module / "__about__.py").read_text())
version = next(
ast.literal_eval(node.value)
for node in about.body
if isinstance(node, ast.Assign)
and any(
isinstance(target, ast.Name) and target.id == "__version__"
for target in node.targets
)
)
wheels = list((directory / "dist").glob(f"{module}-{version}-*.whl"))
if len(wheels) != 1:
raise ValueError(f"Build exactly one {package} {version} wheel first: {wheels}")
return wheels[0]


def verify(wheel: Path, package: str) -> None:
installed = importlib.metadata.distribution(package)
direct = json.loads(installed.read_text("direct_url.json") or "{}")
assert not direct.get("dir_info", {}).get("editable"), direct
digest = hashlib.sha256(wheel.read_bytes()).hexdigest()
assert direct["archive_info"]["hashes"]["sha256"] == digest, direct
module = package.replace("-", "_")
with zipfile.ZipFile(wheel) as archive:
sources = [
name
for name in archive.namelist()
if name.startswith(module + "/") and name.endswith(".py")
]
assert sources
for name in sources:
path = Path(installed.locate_file(name)).resolve()
assert "site-packages" in path.parts, path
assert path.read_bytes() == archive.read(name), path
print(
json.dumps(
{
"package": package,
"version": installed.version,
"wheel": str(wheel),
"sha256": digest,
"verified_sources": len(sources),
}
)
)


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--legacy-plugin", action="store_true")
args = parser.parse_args()
# This PR also repairs the local service simulator. Validate its actual
# built artifact with the SDK/plugin, retaining every test assertion.
packages = [CORE] if args.legacy_plugin else [CORE, OTEL, TESTING]
wheels = [built_wheel(package) for package in packages]
subprocess.run(
[
sys.executable,
"-m",
"pip",
"install",
"--no-index",
"--no-deps",
"--force-reinstall",
*map(str, wheels),
],
check=True,
)
for wheel, package in zip(wheels, packages, strict=True):
verify(wheel, package)
if args.legacy_plugin:
assert importlib.metadata.version(OTEL) == "1.0.0"
import aws_durable_execution_sdk_python_otel as otel

assert "site-packages" in Path(otel.__file__).resolve().parts
subprocess.run([sys.executable, "-m", "pip", "check"], check=True)


if __name__ == "__main__":
main()
11 changes: 9 additions & 2 deletions .github/scripts/tests/test_opentelemetry_conformance_workflow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import re
from pathlib import Path

import yaml


SHARED_WORKFLOW_REF = "bdb4f1cd0f9252c1aaa978bb8b341b71f2b9d9dc"
CONFORMANCE_TEST_REF = "75987d46a915bc37409eed3ea9c3617a924c9756"

WORKFLOW_PATH = (
Path(__file__).parents[2] / "workflows" / "opentelemetry-conformance-tests.yml"
)
Expand All @@ -18,7 +22,10 @@ def test_opentelemetry_conformance_caller_uses_current_workflow_contract() -> No
"uses: aws/aws-durable-execution-conformance-tests/.github/workflows/"
"opentelemetry-orchestrator.yml@"
)
assert orchestrator in workflow
pinned_ref = re.search(re.escape(orchestrator) + r"([0-9a-f]{40})", workflow)
assert pinned_ref is not None
assert pinned_ref.group(1) == SHARED_WORKFLOW_REF
assert f"default: {CONFORMANCE_TEST_REF}" in workflow
assert "python-opentelemetry.yml@" not in workflow
assert "\n otlp_endpoint:" not in workflow

Expand All @@ -28,7 +35,7 @@ def test_opentelemetry_conformance_caller_uses_current_workflow_contract() -> No
"resource_prefix: p",
"sdk_repository: aws/aws-durable-execution-sdk-python",
"sdk_ref: ${{ github.event.pull_request.head.sha || github.sha }}",
"conformance_test_ref: ${{ inputs.conformance_test_ref || 'main' }}",
f"conformance_test_ref: ${{{{ inputs.conformance_test_ref || '{CONFORMANCE_TEST_REF}' }}}}",
"checkout_sdk: true",
f"examples_dir: {EXAMPLES_DIR}",
"adot_release_repository: aws-observability/aws-otel-python-instrumentation",
Expand Down
162 changes: 162 additions & 0 deletions .github/tests/otel_lifecycle_compatibility_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""Exercise real installed version pairs through the public durable handler."""

from __future__ import annotations

import contextvars
from datetime import UTC, datetime
from importlib.metadata import version
import os
from pathlib import Path
import threading
from types import SimpleNamespace

import pytest
from aws_durable_execution_sdk_python import durable_execution
from aws_durable_execution_sdk_python import execution as core_execution
from aws_durable_execution_sdk_python.lambda_service import (
ExecutionDetails,
Operation,
OperationStatus,
OperationType,
)
from aws_durable_execution_sdk_python.plugin import DurableInstrumentationPlugin
from aws_durable_execution_sdk_python_otel.execution_plugin import ExecutionOtelPlugin
from aws_durable_execution_sdk_python_otel.invocation_plugin import InvocationOtelPlugin
from aws_durable_execution_sdk_python_otel.otel_plugin_config import OtelPluginConfig
from opentelemetry import baggage, context, trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from packaging.version import Version


class NoNetworkClient:
def __getattr__(self, name: str):
raise AssertionError(f"Unexpected service access: {name}")


@pytest.mark.parametrize("view", [InvocationOtelPlugin, ExecutionOtelPlugin])
@pytest.mark.parametrize("order", ["alone", "baggage-first", "baggage-last"])
@pytest.mark.parametrize("ambient_present", [False, True])
def test_installed_pair_preserves_context_and_documents_legacy_fallback(
view, order, ambient_present
):
legacy = os.environ.get("OTEL_COMPAT_LEGACY") == "1"
assert Version(version("aws-durable-execution-sdk-python")) >= Version("2.1.0")
assert version("aws-durable-execution-sdk-python-otel") == (
"1.0.0" if legacy else "1.1.0"
)
assert "site-packages" in Path(core_execution.__file__).resolve().parts
import aws_durable_execution_sdk_python_otel as installed_otel

assert "site-packages" in Path(installed_otel.__file__).resolve().parts

def run() -> None:
provider = TracerProvider()
exporter = InMemorySpanExporter()
provider.add_span_processor(SimpleSpanProcessor(exporter))
tracer = provider.get_tracer("installed-lifecycle")
phases = []
worker_threads = []
caller_thread = threading.get_ident()

class BaggagePlugin(DurableInstrumentationPlugin):
def on_invocation_start(self, info):
self.token = context.attach(baggage.set_baggage("customer", "present"))
phases.append("baggage-start")
worker_threads.append(threading.get_ident())

def on_invocation_end(self, info):
context.detach(self.token)
phases.append("baggage-end")
worker_threads.append(threading.get_ident())

plugin = view(
OtelPluginConfig(
tracer_provider=provider,
enrich_logger=False,
context_extractor=lambda _: None,
)
)
plugins = {
"alone": [plugin],
"baggage-first": [BaggagePlugin(), plugin],
"baggage-last": [plugin, BaggagePlugin()],
}[order]
body_parents = []

@durable_execution(plugins=plugins, boto3_client=NoNetworkClient())
def handler(event, durable_context):
phases.append("body")
worker_threads.append(threading.get_ident())
assert baggage.get_baggage("incoming") == "keep"
assert baggage.get_baggage("customer") == (
None if order == "alone" else "present"
)
parent = trace.get_current_span().get_span_context()
body_parents.append(parent)
with tracer.start_as_current_span("customer-span"):
pass
return "ok"

operation = Operation(
operation_id="installed",
operation_type=OperationType.EXECUTION,
status=OperationStatus.STARTED,
start_timestamp=datetime(2026, 10, 8, tzinfo=UTC),
execution_details=ExecutionDetails(input_payload="{}"),
)
event = {
"DurableExecutionArn": "test-arn/installed",
"CheckpointToken": "token",
"InitialExecutionState": {
"Operations": [operation.to_json_dict()],
"NextMarker": "",
},
}
lambda_context = SimpleNamespace(
aws_request_id="installed",
client_context=None,
identity=None,
_epoch_deadline_time_in_ms=0,
invoked_function_arn="test-arn",
tenant_id=None,
)
ambient = tracer.start_span("host") if ambient_present else trace.INVALID_SPAN
host = baggage.set_baggage(
"incoming", "keep", trace.set_span_in_context(ambient)
)
token = context.attach(host)
try:
for _ in range(2):
assert handler(event, lambda_context)["Status"] == "SUCCEEDED"
assert context.get_current() is host
assert caller_thread not in worker_threads
assert phases == (
["body"] * 2
if order == "alone"
else ["baggage-start", "body", "baggage-end"] * 2
)
spans = exporter.get_finished_spans()
if legacy and view is InvocationOtelPlugin:
# The released plugin does not attach an Invocation fallback.
assert body_parents == [ambient.get_span_context()] * 2
else:
name = "Invocation" if view is InvocationOtelPlugin else "Workflow"
contexts = [span.context for span in spans if span.name == name]
assert contexts
assert all(
parent.is_valid and parent in contexts for parent in body_parents
)
users = [span for span in spans if span.name == "customer-span"]
assert len(users) == 2
assert [span.parent for span in users] == [
parent if parent.is_valid else None for parent in body_parents
]
assert plugin._context_tokens == {}
finally:
context.detach(token)
ambient.end()
provider.shutdown()

contextvars.Context().run(run)
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
branches: [ main ]

pull_request:
branches: [ main ]
branches: ["main"]

jobs:
lint-commits:
Expand Down Expand Up @@ -84,8 +84,6 @@ jobs:
run: hatch run types:check
- name: Run tests + coverage
run: hatch run test:cov
- name: Verify supported legacy core compatibility
run: hatch run test-pypi-otel-legacy:test
- name: Build distribution
run: |
for pkg in packages/*/; do
Expand All @@ -96,6 +94,10 @@ jobs:
cd "$GITHUB_WORKSPACE"
fi
done
- name: Test installed core and OTel wheels
run: hatch run test-wheel-otel:test
- name: Test released OTel with the new core wheel
run: hatch run test-wheel-otel-legacy:test
- name: Verify OTel wheel dependency contract
run: |
OTEL_WHEEL=$(find packages/aws-durable-execution-sdk-python-otel/dist \
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/cloud-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ jobs:
echo "Could not resolve the latest ADOT Python layer for $AWS_REGION"
exit 1
fi
aws lambda get-layer-version-by-arn \
# Parallel jobs can throttle this read; keep retries local to the lookup.
AWS_RETRY_MODE=standard AWS_MAX_ATTEMPTS=8 \
aws lambda get-layer-version-by-arn \
--arn "$ADOT_LAYER_ARN" \
--region "$AWS_REGION" \
--query LayerVersionArn \
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/opentelemetry-conformance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ on:
conformance_test_ref:
description: Conformance test commit SHA or branch name
required: true
default: main
default: 75987d46a915bc37409eed3ea9c3617a924c9756
type: string

# Backend stacks are shared across PRs. Queue whole runs so reusable
Expand All @@ -66,14 +66,14 @@ jobs:
actions: write
contents: read
id-token: write
uses: aws/aws-durable-execution-conformance-tests/.github/workflows/opentelemetry-orchestrator.yml@a66037abbbfa55fde97f714e30f0bc262edefd63
uses: aws/aws-durable-execution-conformance-tests/.github/workflows/opentelemetry-orchestrator.yml@bdb4f1cd0f9252c1aaa978bb8b341b71f2b9d9dc
with:
language: python
runs_on: codebuild-github-actions-runner-${{ github.run_id }}-${{ github.run_attempt }}
resource_prefix: p
sdk_repository: aws/aws-durable-execution-sdk-python
sdk_ref: ${{ github.event.pull_request.head.sha || github.sha }}
conformance_test_ref: ${{ inputs.conformance_test_ref || 'main' }}
conformance_test_ref: ${{ inputs.conformance_test_ref || '75987d46a915bc37409eed3ea9c3617a924c9756' }}
# Check the SDK out so the handlers and templates below are on disk. The handlers
# themselves are installed from sdk_ref by src/requirements.txt during the SAM build.
checkout_sdk: true
Expand Down
22 changes: 16 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,25 @@ hatch run dev-otel:typecheck # type check otel only
hatch run dev-examples:test # run examples tests only
```

### PyPI release testing
### Installed package compatibility testing

To verify packages work against the published PyPI version of the core SDK (rather than the local workspace):
Build the core, OTel and testing-library distributions with `hatch build` in each package, then run
these commands from the repository root:

```bash
hatch run test-pypi-otel:test # test new OTel capabilities against capable installed core
hatch run test-pypi-otel-legacy:test # valid registrations/lifecycles on supported core 2.0.x
hatch run test-pypi-examples:test # test examples against PyPI core SDK
```
hatch run test-wheel-otel:test # full OTel suite on the three built wheels
hatch run test-wheel-otel-legacy:test # released OTel 1.0.0 with the built core
hatch run test-pypi-examples:test # examples against the published core
```

The wheel lanes have no editable workspace members. They verify installed source
bytes and artifact hashes before exercising public handlers. OTel 1.1 requires
the redesigned core 2.1.0 lifecycle; it no longer claims compatibility with core
2.0.x. Publish core first. Building both wheels lets CI verify the intended pair
before that minimum is available on PyPI. The legacy-plugin lane documents the
actual core-only upgrade: host isolation is provided by the new core, while old
Invocation OTel does not gain the new fallback. Workspace tests continue to cover
the complete current implementation with `hatch run dev-otel:test`.

### Package-level commands

Expand Down
Loading
Loading