Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
562f9c3
feat(plugin): prepare chained-invoke propagation metadata
Oct 2, 2026
98ad579
feat: propagate operation trace context on chained invoke START
zhongkechen Oct 5, 2026
5ae091e
fix(ci): route review intake through read-only resolver
zhongkechen Oct 6, 2026
b744341
fix: account for chained invoke options in checkpoint batch sizes
zhongkechen Oct 6, 2026
8b98e6d
Merge main for CodeBuild CI and isolated OTel test environment
zhongkechen Oct 6, 2026
9ed7612
Merge main after factory migration revert
Oct 6, 2026
8196cd1
Preserve pending conformance and E2E CI runs
Oct 6, 2026
5f83d47
ci: serialize revised OTel callers without replacing pending work
zhongkechen Oct 7, 2026
7b83565
ci: run OTel conformance on validated CodeBuild Java 21
Oct 7, 2026
e823db4
Merge main OpenTelemetry compatibility fix into propagation groundwork
Oct 7, 2026
93cea69
Merge main exclusive OpenTelemetry view validation
Oct 7, 2026
ec7ea25
Merge main CI runner updates into PR branch
Oct 7, 2026
c91e61a
ci: pin OTel queue workflow to published squash commit
Oct 7, 2026
4bc9e90
Merge main CI workflow reference repair (#786)
zhongkechen Oct 13, 2026
df4a3ee
Merge approved main1064 into PR #768 propagation groundwork
zhongkechen Oct 13, 2026
d3a1258
Merge main OpenTelemetry roots into chained-invoke propagation draft
zhongkechen Oct 13, 2026
25fa286
Merge branch 'main' into feature/propagation-groundwork-764
zhongkechen Oct 13, 2026
5c29857
Merge current main shared workflow references
zhongkechen Oct 13, 2026
d7750e2
Merge main dependency and workflow updates into PR #768
zhongkechen Oct 14, 2026
beafebf
Pin the Java formatter independently of the Spotless plugin
zhongkechen Oct 14, 2026
83e543b
Merge the verified formatter policy fix into PR #768
zhongkechen Oct 14, 2026
f2cd1c2
Format existing Java sources with the updated default formatter
zhongkechen Oct 14, 2026
fe40cb5
Merge the default formatter repair into PR #768
zhongkechen Oct 14, 2026
5dd9311
Merge the accepted formatter repair from main
zhongkechen Oct 14, 2026
402512b
Update the AWS SDK BOM to the official 2.56.0 release
zhongkechen Oct 14, 2026
ea74699
Isolate optional propagation linkage failures and refresh model guidance
zhongkechen Oct 14, 2026
7e7afaf
docs: update chained invoke model availability
zhongkechen Oct 14, 2026
664d477
fix(otel): require authoritative invoke sampling metadata
zhongkechen Oct 14, 2026
32997b9
Merge main 2a7ce590 into Java PR768 for OpenTelemetry 1.67
zhongkechen Oct 14, 2026
4aaeabf
Merge main 2b2f930 into Java PR768 for checkpoint token revocation
zhongkechen Oct 14, 2026
e9757be
Merge main into PR #768
zhongkechen Oct 16, 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
77 changes: 77 additions & 0 deletions docs/advanced/propagation-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Chained-invoke trace propagation

For a new `CHAINED_INVOKE` operation, `InvokeOperation.startInvocation()` collects optional metadata after
`onOperationStart` has established the operation's span and before checkpoint transport serialization. The customer
payload first uses its existing serializer, so a payload failure does not trigger optional metadata collection. It writes
that header through the generated `ChainedInvokeOptions.builder().xAmznTraceId(...)` method. The wire member is the
flat optional `XAmznTraceId`; there is no nested `PropagationMetadata` structure or W3C transport field.

Function name, tenant ID, payload, operation name, ID, parent ID and checkpoint behavior retain their existing
paths. Each operation carries its own header, including when several invokes share a checkpoint request. No
execution-global or HTTP-request trace header can substitute for those distinct calling-operation parents.

## Plugin contract and fallback

`PropagationInput` is an immutable SDK-owned snapshot with `executionArn`, `operationId`, optional `parentOperationId`,
and `targetFunctionName`. `PropagationMetadata` is an immutable SDK-owned contribution with optional `xAmznTraceId`.
Both types use only Java strings; generated Lambda and OTel model types do not enter the plugin interface.
The typed default hook adapts a String-only producer overload, so bundled plugin layers do not reference newly added
core classes. This does not remove the bundled plugins' existing requirement for the same-thread invocation-hook
capability: cores without it, including released 2.2.1, are rejected at construction. Collection also requires a core
that implements this hook; registration/provider API versions and existing hooks are unchanged.

The optional synchronous `providePropagationMetadata` default method returns null. The dispatcher supplies the same
immutable snapshot in configured order. The first non-null supported member wins; equal values do not conflict.
Different later values warn with the winning and conflicting plugin identities and a conflict count, without logging
the header. Null contributions abstain; blank headers and ordinary hook failures are logged and skipped. A missing
contribution omits the transport member so the backend can retain its existing inherited-header fallback.
The collector follows the event hooks' isolation policy: `Exception` (including `CancellationException`) and
`LinkageError` are logged and skipped, so an incompatible optional plugin does not prevent later contributions or
the invoke START checkpoint. Other `Error` types, including JVM-fatal errors, propagate with their original identity.
Runtime exceptions from the logging backend do not interrupt collection.

Both OTel views encode the resolved canonical trace ID, actual calling-operation span ID and sampling decision as
`Root=1-<8 hex>-<24 hex>;Parent=<16 hex>;Sampled=0|1`. They require active invocation ownership. The producer does not
create a span or sample again, and retains the configured provider/resource and upstream-parent behavior. Explicit
upstream `Sampled=0` is preserved. The producer can derive an initial operation ID before a start hook, but the real
invoke path collects after that hook and therefore uses the operation context it already established.

The invocation view can read the sampling flags from an operation span that has already started. The execution view
defers that span until completion, so its placeholder does not establish a per-span sampling decision. It contributes
metadata only when the visible provider uses `DurableSampler`, including the application-owned builder setup. It
abstains for replacement samplers and opaque providers whose effective sampler cannot be verified, including opaque
agent facades. In those cases the backend's omitted-header fallback applies; no new invoke-parent linkage is promised.
The invocation view applies the same restriction if asked before an operation span is available.
This does not change the configured sampler, create an early operation span, or query the sampler again.

## Replay and retry

Replaying a stored START polls for the existing operation; replaying a terminal operation returns or raises its
stored outcome. Neither path collects metadata or sends another START. If a START checkpoint failed or was lost before
commit, a later invocation can create it again and collect again. Plugins must be deterministic and side-effect free;
this hook is not exactly-once delivery. Checkpoint batching and retries preserve each submitted operation's options.

## Model and backend dependencies

The [`aws.sdk.version` property in the root POM](../../pom.xml) selects AWS SDK for Java **2.56.0**. Its official Lambda
model defines optional `ChainedInvokeOptions.XAmznTraceId` on the chained-invoke START update. The published Maven
artifact provides the typed builder setter and getter. Direct checks of its generated marshaller preserve sampled
and unsampled headers through model copies and omit an absent field instead of sending null. The model update has
also passed the [normal CI build on Java 17, 21 and 25](https://github.com/aws/aws-durable-execution-sdk-java/actions/runs/38635334261/attempts/2).
No copied model artifact, reflection fallback, serializer replacement or raw HTTP field injection is required.

Distributed-map propagation is a separate path. This Java SDK currently exposes no distributed-map operation or
START dispatch; its existing `map` and `parallel` APIs use CONTEXT operations. No distributed-map, fanout or HTTP API
is introduced here. A future high-level distributed-map operation must integrate its corresponding model path.

Published-model, serialization and build verification are separate from deployed backend behavior. Parent/child traces
for durable and ordinary Lambda targets still require cloud validation of header persistence and forwarding. That
coverage is tracked in [shared conformance PR #131](https://github.com/aws/aws-durable-execution-conformance-tests/pull/131).
The client-side checks above do not claim that deployed trace topology has been verified. This remains related to
issue #764; runtime inbound headers and plugin-instance lifetime are separate changes.

Tests exercise real public invoke and checkpoint paths, pending/terminal replay, failed uncommitted START recovery,
ordinary and linkage-error plugin fallbacks and other Error propagation, sampled/unsampled OTel views, batched invokes
with distinct parents, custom payload and tenant preservation, generated-model copies, and the normal Lambda client's JSON marshaller with
only HTTP transport replaced. Batch-boundary tests include the encoded options and trace header in the 750 KiB size
budget, including JSON escaping and UTF-8. Generated-model checks use the official published dependency.
14 changes: 14 additions & 0 deletions otel-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ If you configure your own `SdkTracerProviderBuilder`, add the OpenTelemetry SDK
</dependency>
```

## Chained-invoke propagation

Both views provide the calling operation's X-Ray context to the SDK's synchronous collector. New invoke START
checkpoints carry that context in the flat optional `ChainedInvokeOptions.XAmznTraceId` field. The root POM's
AWS SDK for Java 2.56.0 provides the generated typed setter. Deployed parent/child trace behavior still requires
cloud validation. See [the implemented path and validation status](../docs/advanced/propagation-metadata.md).

Execution-view operation spans are deferred until completion. Their placeholders can provide sampling metadata only
with a visible provider using the SDK's `DurableSampler`, as configured by the application-owned builder constructor.
Replacement samplers and opaque provider/agent facades therefore cause this view to abstain; the backend's existing
omitted-header fallback applies. Invocation view uses the actual started operation span's flags; before that span is
available, it applies the same sampling-policy restriction. Neither path changes
the customer's sampler or creates a span just to obtain a sampling decision.

## Fallback execution roots

When the backend supplies no complete remote parent and the installed SDK sampler selects `RECORD_AND_SAMPLE`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,28 @@ private void finishInvocationEnd(InvocationEndInfo info) {
samplingIntent = null;
}

/** Produces metadata without creating spans or changing the configured provider/resource. */
@Override
public String providePropagationMetadata(
String executionArn, String operationId, String parentOperationId, String targetFunctionName) {
if (!tracingEnabled || !executionArn.equals(durableExecutionArn)) return null;
var trace = executionTrace;
if (trace == null) return null;
// These contexts are deferred placeholders, not sampled operation spans. A replacement sampler may decide
// differently for Invocation and invoke; an opaque provider does not expose its effective policy.
if (sdkTracerProvider == null || !OtelPluginSupport.usesDurableSamplingBridge(sdkTracerProvider)) return null;
var context = operationContexts.get(operationId);
if (context == null) {
// Before a new operation starts, use the same deterministic ID its initial span will receive.
context = SpanContext.create(
trace.traceId(),
idGenerator.generateSpanIdForOperation(durableExecutionArn, operationId),
effectiveTraceFlags(),
effectiveTraceState());
}
return OtelPropagationMetadata.fromContext(context);
}

// ─── Operation hooks ─────────────────────────────────────────────────

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,30 @@ private void finishInvocationEnd(InvocationEndInfo info) {
samplingIntent = null;
}

/** Produces metadata without creating spans or changing the configured provider/resource. */
@Override
public String providePropagationMetadata(
String executionArn, String operationId, String parentOperationId, String targetFunctionName) {
if (!tracingEnabled || !executionArn.equals(durableExecutionArn)) return null;
var trace = executionTrace;
if (trace == null) return null;
// An observed operation's actual context is authoritative, including invocation-view continuation segments.
var context = operationContexts.get(operationId);
if (context == null) {
// A planned context has no actual span decision yet. Preserve abstention when the effective sampler
// cannot be verified; normal invoke collection runs after the operation's Start hook.
if (sdkTracerProvider == null || !OtelPluginSupport.usesDurableSamplingBridge(sdkTracerProvider))
return null;
// Before a new operation starts, use the same deterministic ID its initial span will receive.
context = SpanContext.create(
trace.traceId(),
idGenerator.generateSpanIdForOperation(durableExecutionArn, operationId),
effectiveTraceFlags(),
effectiveTraceState());
}
return OtelPropagationMetadata.fromContext(context);
}

// ─── Operation hooks ─────────────────────────────────────────────────

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.otel;

import io.opentelemetry.api.trace.SpanContext;

/** Pure encoding of an existing operation context into the reviewed X-Ray carrier shape. */
final class OtelPropagationMetadata {
private OtelPropagationMetadata() {}

static String fromContext(SpanContext context) {
if (context == null || !context.isValid()) return null;
var trace = context.getTraceId();
var header = "Root=1-" + trace.substring(0, 8) + "-" + trace.substring(8) + ";Parent=" + context.getSpanId()
+ ";Sampled=" + (context.isSampled() ? "1" : "0");
return header;
}
}
Loading
Loading