diff --git a/conformance-tests/src/main/java/plugin/PluginAttemptInfoShape.java b/conformance-tests/src/main/java/plugin/PluginAttemptInfoShape.java
new file mode 100644
index 000000000..d6cfc7aef
--- /dev/null
+++ b/conformance-tests/src/main/java/plugin/PluginAttemptInfoShape.java
@@ -0,0 +1,194 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+package plugin;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Locale;
+import software.amazon.lambda.durable.DurableConfig;
+import software.amazon.lambda.durable.DurableContext;
+import software.amazon.lambda.durable.DurableHandler;
+import software.amazon.lambda.durable.config.StepConfig;
+import software.amazon.lambda.durable.plugin.DurableExecutionPlugin;
+import software.amazon.lambda.durable.plugin.InvocationInfo;
+import software.amazon.lambda.durable.plugin.UserFunctionEndInfo;
+import software.amazon.lambda.durable.plugin.UserFunctionStartInfo;
+import software.amazon.lambda.durable.retry.JitterStrategy;
+import software.amazon.lambda.durable.retry.RetryStrategies;
+
+/**
+ * 10-21: Attempt hook info field shape (CANONICAL DUMP).
+ *
+ *
A single step named {@code "flaky"} that throws on attempt 1 and succeeds on attempt 2 using the SDK's real
+ * exponential-backoff retry strategy (max attempts 3, ~1s delay), returning {@code "ok"}. The instrumentation plugin
+ * (filtering to step-type attempts) emits ONE single-line JSON record per per-attempt (user-function) hook event: a
+ * canonical dump of that hook's OWN info parameter, every exposed component mapped to its canonical camelCase name,
+ * null / unexposed fields OMITTED.
+ *
+ *
Java's {@link UserFunctionStartInfo} exposes id/name/type/subType/parentId/startTimestamp/isReplayingChildren/
+ * attempt (no endTimestamp/isReplay/outcome/error at start). Java's {@link UserFunctionEndInfo} adds endTimestamp, the
+ * {@code succeeded} boolean (presented as the shared {@code outcome} SUCCEEDED/FAILED token) and {@code error}. This is
+ * the richest attempt surface — every probed field is present, so the attempt assertions are expected to pass.
+ */
+@SuppressWarnings("deprecation")
+public class PluginAttemptInfoShape extends DurableHandler {
+
+ @Override
+ protected DurableConfig createConfiguration() {
+ return DurableConfig.builder().withPlugins(new AttemptShapePlugin()).build();
+ }
+
+ @Override
+ public String handleRequest(Object input, DurableContext context) {
+ return context.step(
+ "flaky",
+ String.class,
+ stepCtx -> {
+ // Fail on the first attempt, succeed on the second, using the SDK's built-in 1-based attempt
+ // number.
+ if (stepCtx.getAttempt() < 2) {
+ throw new RuntimeException("Attempt " + stepCtx.getAttempt() + " failed");
+ }
+ return "ok";
+ },
+ StepConfig.builder()
+ .retryStrategy(RetryStrategies.exponentialBackoff(
+ 3, Duration.ofSeconds(1), Duration.ofSeconds(10), 1.0, JitterStrategy.NONE))
+ .build());
+ }
+
+ private static final class AttemptShapePlugin implements DurableExecutionPlugin {
+ private volatile String executionArn;
+
+ @Override
+ public void onInvocationStart(InvocationInfo info) {
+ this.executionArn = info.durableExecutionArn();
+ }
+
+ @Override
+ public void onUserFunctionStart(UserFunctionStartInfo info) {
+ if (!PluginSupport.isStep(info.type()) || info.attempt() == null) {
+ return;
+ }
+ new Rec("attempt-start")
+ .str("id", info.id())
+ .str("name", info.name())
+ .str("type", Rec.upper(info.type()))
+ .str("subType", info.subType())
+ .str("parentId", info.parentId())
+ .num("attempt", info.attempt())
+ .time("startTimestamp", info.startTimestamp())
+ .bool("isReplayingChildren", info.isReplayingChildren())
+ .emit(executionArn);
+ }
+
+ @Override
+ public void onUserFunctionEnd(UserFunctionEndInfo info) {
+ if (!PluginSupport.isStep(info.type()) || info.attempt() == null) {
+ return;
+ }
+ new Rec("attempt-end")
+ .str("id", info.id())
+ .str("name", info.name())
+ .str("type", Rec.upper(info.type()))
+ .str("subType", info.subType())
+ .str("parentId", info.parentId())
+ .num("attempt", info.attempt())
+ .time("startTimestamp", info.startTimestamp())
+ .time("endTimestamp", info.endTimestamp())
+ .bool("isReplayingChildren", info.isReplayingChildren())
+ .str("outcome", info.succeeded() ? "SUCCEEDED" : "FAILED")
+ .str("error", Rec.msg(info.error()))
+ .emit(executionArn);
+ }
+ }
+
+ /** Single-line JSON record builder: emits every provided key, skipping nulls, then stamps durableExecutionArn. */
+ private static final class Rec {
+ private final StringBuilder sb = new StringBuilder("{");
+
+ Rec(String hook) {
+ raw("plugin", "\"CONFPLUGIN\"");
+ raw("hook", "\"" + hook + "\"");
+ }
+
+ Rec str(String key, String value) {
+ if (value != null) {
+ raw(key, quote(value));
+ }
+ return this;
+ }
+
+ Rec num(String key, Integer value) {
+ if (value != null) {
+ raw(key, value.toString());
+ }
+ return this;
+ }
+
+ Rec bool(String key, boolean value) {
+ raw(key, value ? "true" : "false");
+ return this;
+ }
+
+ Rec time(String key, Instant value) {
+ if (value != null) {
+ raw(key, quote(value.toString()));
+ }
+ return this;
+ }
+
+ private void raw(String key, String jsonValue) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append('"').append(key).append("\": ").append(jsonValue);
+ }
+
+ void emit(String executionArn) {
+ System.out.println(sb.append(PluginSupport.arnField(executionArn)).append('}'));
+ }
+
+ static String upper(String s) {
+ return s == null ? null : s.toUpperCase(Locale.ROOT);
+ }
+
+ static String msg(Throwable t) {
+ if (t == null) {
+ return null;
+ }
+ return t.getMessage() != null ? t.getMessage() : t.toString();
+ }
+
+ static String quote(String s) {
+ StringBuilder b = new StringBuilder("\"");
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '"':
+ b.append("\\\"");
+ break;
+ case '\\':
+ b.append("\\\\");
+ break;
+ case '\n':
+ b.append("\\n");
+ break;
+ case '\r':
+ b.append("\\r");
+ break;
+ case '\t':
+ b.append("\\t");
+ break;
+ default:
+ if (c < 0x20) {
+ b.append(String.format("\\u%04x", (int) c));
+ } else {
+ b.append(c);
+ }
+ }
+ }
+ return b.append('"').toString();
+ }
+ }
+}
diff --git a/conformance-tests/src/main/java/plugin/PluginInvocationInfoShape.java b/conformance-tests/src/main/java/plugin/PluginInvocationInfoShape.java
new file mode 100644
index 000000000..c306c80da
--- /dev/null
+++ b/conformance-tests/src/main/java/plugin/PluginInvocationInfoShape.java
@@ -0,0 +1,149 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+package plugin;
+
+import java.time.Duration;
+import java.time.Instant;
+import software.amazon.lambda.durable.DurableConfig;
+import software.amazon.lambda.durable.DurableContext;
+import software.amazon.lambda.durable.DurableHandler;
+import software.amazon.lambda.durable.plugin.DurableExecutionPlugin;
+import software.amazon.lambda.durable.plugin.InvocationEndInfo;
+import software.amazon.lambda.durable.plugin.InvocationInfo;
+import software.amazon.lambda.durable.plugin.InvocationStatus;
+
+/**
+ * 10-19: Invocation hook info field shape (CANONICAL DUMP).
+ *
+ * A single 2-second wait that then returns {@code "done-" + input}. The instrumentation plugin emits ONE single-line
+ * JSON record per invocation hook event: a canonical dump of that hook's OWN info parameter, every exposed component
+ * mapped one-to-one to its canonical camelCase name, null / unexposed fields OMITTED (a missing key fails its assertion
+ * — the parity signal).
+ *
+ *
Java's {@link InvocationInfo} exposes only {@code requestId}, {@code executionStartTime} (→
+ * {@code executionStartTimestamp}) and {@code isFirstInvocation}; it does NOT expose the execution input, the
+ * operations map, or an externally-updated-operations collection, so {@code executionInput}, {@code operationsCount}
+ * and {@code updatedOperationsCount} are absent. Java's {@link InvocationEndInfo} exposes {@code isFirstInvocation},
+ * {@code invocationStatus} (→ {@code status}) and {@code executionError}; it does NOT expose the execution input or the
+ * final result, so {@code executionInput} and {@code executionResult} are absent. The single derived scalar
+ * {@code terminal} := status in (SUCCEEDED, FAILED). Those omissions are the honest reds the requirement produces.
+ */
+@SuppressWarnings("deprecation")
+public class PluginInvocationInfoShape extends DurableHandler {
+
+ @Override
+ protected DurableConfig createConfiguration() {
+ return DurableConfig.builder().withPlugins(new InvocationShapePlugin()).build();
+ }
+
+ @Override
+ public String handleRequest(String input, DurableContext context) {
+ context.wait(null, Duration.ofSeconds(2));
+ return "done-" + input;
+ }
+
+ private static final class InvocationShapePlugin implements DurableExecutionPlugin {
+ private volatile String executionArn;
+
+ @Override
+ public void onInvocationStart(InvocationInfo info) {
+ this.executionArn = info.durableExecutionArn();
+ new Rec("invocation-start")
+ .bool("isFirstInvocation", info.isFirstInvocation())
+ .str("requestId", info.requestId())
+ .time("executionStartTimestamp", info.executionStartTime())
+ .emit(executionArn);
+ }
+
+ @Override
+ public void onInvocationEnd(InvocationEndInfo info) {
+ InvocationStatus status = info.invocationStatus();
+ boolean terminal = status == InvocationStatus.SUCCEEDED || status == InvocationStatus.FAILED;
+ new Rec("invocation-end")
+ .bool("isFirstInvocation", info.isFirstInvocation())
+ .str("requestId", info.requestId())
+ .str("status", status == null ? null : status.name())
+ .bool("terminal", terminal)
+ .str("executionError", Rec.msg(info.executionError()))
+ .emit(executionArn);
+ }
+ }
+
+ /** Single-line JSON record builder: emits every provided key, skipping nulls, then stamps durableExecutionArn. */
+ private static final class Rec {
+ private final StringBuilder sb = new StringBuilder("{");
+
+ Rec(String hook) {
+ raw("plugin", "\"CONFPLUGIN\"");
+ raw("hook", "\"" + hook + "\"");
+ }
+
+ Rec str(String key, String value) {
+ if (value != null) {
+ raw(key, quote(value));
+ }
+ return this;
+ }
+
+ Rec bool(String key, boolean value) {
+ raw(key, value ? "true" : "false");
+ return this;
+ }
+
+ Rec time(String key, Instant value) {
+ if (value != null) {
+ raw(key, quote(value.toString()));
+ }
+ return this;
+ }
+
+ private void raw(String key, String jsonValue) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append('"').append(key).append("\": ").append(jsonValue);
+ }
+
+ void emit(String executionArn) {
+ System.out.println(sb.append(PluginSupport.arnField(executionArn)).append('}'));
+ }
+
+ static String msg(Throwable t) {
+ if (t == null) {
+ return null;
+ }
+ return t.getMessage() != null ? t.getMessage() : t.toString();
+ }
+
+ static String quote(String s) {
+ StringBuilder b = new StringBuilder("\"");
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '"':
+ b.append("\\\"");
+ break;
+ case '\\':
+ b.append("\\\\");
+ break;
+ case '\n':
+ b.append("\\n");
+ break;
+ case '\r':
+ b.append("\\r");
+ break;
+ case '\t':
+ b.append("\\t");
+ break;
+ default:
+ if (c < 0x20) {
+ b.append(String.format("\\u%04x", (int) c));
+ } else {
+ b.append(c);
+ }
+ }
+ }
+ return b.append('"').toString();
+ }
+ }
+}
diff --git a/conformance-tests/src/main/java/plugin/PluginOperationChangeShape.java b/conformance-tests/src/main/java/plugin/PluginOperationChangeShape.java
new file mode 100644
index 000000000..7d73a6093
--- /dev/null
+++ b/conformance-tests/src/main/java/plugin/PluginOperationChangeShape.java
@@ -0,0 +1,169 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+package plugin;
+
+import java.time.Instant;
+import java.util.Locale;
+import software.amazon.lambda.durable.DurableConfig;
+import software.amazon.lambda.durable.DurableContext;
+import software.amazon.lambda.durable.DurableHandler;
+import software.amazon.lambda.durable.plugin.DurableExecutionPlugin;
+import software.amazon.lambda.durable.plugin.InvocationInfo;
+import software.amazon.lambda.durable.plugin.OperationChangeInfo;
+import software.amazon.lambda.durable.plugin.OperationChangeItemInfo;
+
+/**
+ * 10-22: Operation-change hook info field shape (CANONICAL DUMP).
+ *
+ * A single step named {@code "greet"} returning the constant {@code "task-a"}. For each step-type operation in the
+ * change info's updated-operations delta the instrumentation plugin emits ONE single-line JSON record: a canonical dump
+ * of that DELTA ITEM's OWN field surface, plus the hook-level fields {@code executionArn} (from the change info),
+ * {@code updatedOperationsCount}/{@code operationsCount} (map sizes) and the derived {@code inFullMap} := the same id
+ * also appears in the info's full operations map. Null / unexposed fields are OMITTED.
+ *
+ *
Java's {@link OperationChangeItemInfo} exposes id/name/type/subType/parentId/startTimestamp/endTimestamp/error/
+ * status but does NOT expose the checkpointed serialized result, the attempt number, or a replay indicator, so
+ * {@code result}, {@code attempt} and {@code isReplay} are absent on each item — those omissions are the honest reds
+ * the requirement produces.
+ */
+@SuppressWarnings("deprecation")
+public class PluginOperationChangeShape extends DurableHandler {
+
+ @Override
+ protected DurableConfig createConfiguration() {
+ return DurableConfig.builder().withPlugins(new ChangeShapePlugin()).build();
+ }
+
+ @Override
+ public String handleRequest(Object input, DurableContext context) {
+ return context.step("greet", String.class, stepCtx -> "task-a");
+ }
+
+ private static final class ChangeShapePlugin implements DurableExecutionPlugin {
+ private volatile String executionArn;
+
+ @Override
+ public void onInvocationStart(InvocationInfo info) {
+ this.executionArn = info.durableExecutionArn();
+ }
+
+ @Override
+ public void onOperationChange(OperationChangeInfo info) {
+ int updatedOperationsCount = info.updatedOperations().size();
+ int operationsCount = info.operations().size();
+ for (OperationChangeItemInfo item : info.updatedOperations().values()) {
+ if (!PluginSupport.isStepChange(item.type())) {
+ continue;
+ }
+ new Rec("operation-change")
+ .str("executionArn", info.durableExecutionArn())
+ .num("updatedOperationsCount", updatedOperationsCount)
+ .num("operationsCount", operationsCount)
+ .bool("inFullMap", info.operations().containsKey(item.id()))
+ .str("id", item.id())
+ .str("name", item.name())
+ .str("type", Rec.upper(item.type()))
+ .str("subType", item.subType())
+ .str("parentId", item.parentId())
+ .str(
+ "status",
+ item.status() == null
+ ? null
+ : Rec.upper(item.status().toString()))
+ .time("startTimestamp", item.startTimestamp())
+ .time("endTimestamp", item.endTimestamp())
+ .str("error", Rec.msg(item.error()))
+ .emit(executionArn);
+ }
+ }
+ }
+
+ /** Single-line JSON record builder: emits every provided key, skipping nulls, then stamps durableExecutionArn. */
+ private static final class Rec {
+ private final StringBuilder sb = new StringBuilder("{");
+
+ Rec(String hook) {
+ raw("plugin", "\"CONFPLUGIN\"");
+ raw("hook", "\"" + hook + "\"");
+ }
+
+ Rec str(String key, String value) {
+ if (value != null) {
+ raw(key, quote(value));
+ }
+ return this;
+ }
+
+ Rec num(String key, Integer value) {
+ if (value != null) {
+ raw(key, value.toString());
+ }
+ return this;
+ }
+
+ Rec bool(String key, boolean value) {
+ raw(key, value ? "true" : "false");
+ return this;
+ }
+
+ Rec time(String key, Instant value) {
+ if (value != null) {
+ raw(key, quote(value.toString()));
+ }
+ return this;
+ }
+
+ private void raw(String key, String jsonValue) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append('"').append(key).append("\": ").append(jsonValue);
+ }
+
+ void emit(String executionArn) {
+ System.out.println(sb.append(PluginSupport.arnField(executionArn)).append('}'));
+ }
+
+ static String upper(String s) {
+ return s == null ? null : s.toUpperCase(Locale.ROOT);
+ }
+
+ static String msg(Throwable t) {
+ if (t == null) {
+ return null;
+ }
+ return t.getMessage() != null ? t.getMessage() : t.toString();
+ }
+
+ static String quote(String s) {
+ StringBuilder b = new StringBuilder("\"");
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '"':
+ b.append("\\\"");
+ break;
+ case '\\':
+ b.append("\\\\");
+ break;
+ case '\n':
+ b.append("\\n");
+ break;
+ case '\r':
+ b.append("\\r");
+ break;
+ case '\t':
+ b.append("\\t");
+ break;
+ default:
+ if (c < 0x20) {
+ b.append(String.format("\\u%04x", (int) c));
+ } else {
+ b.append(c);
+ }
+ }
+ }
+ return b.append('"').toString();
+ }
+ }
+}
diff --git a/conformance-tests/src/main/java/plugin/PluginOperationInfoShape.java b/conformance-tests/src/main/java/plugin/PluginOperationInfoShape.java
new file mode 100644
index 000000000..71bfce0c0
--- /dev/null
+++ b/conformance-tests/src/main/java/plugin/PluginOperationInfoShape.java
@@ -0,0 +1,176 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+package plugin;
+
+import java.time.Instant;
+import java.util.Locale;
+import software.amazon.lambda.durable.DurableConfig;
+import software.amazon.lambda.durable.DurableContext;
+import software.amazon.lambda.durable.DurableHandler;
+import software.amazon.lambda.durable.plugin.DurableExecutionPlugin;
+import software.amazon.lambda.durable.plugin.InvocationInfo;
+import software.amazon.lambda.durable.plugin.OperationEndInfo;
+import software.amazon.lambda.durable.plugin.OperationInfo;
+
+/**
+ * 10-20: Operation hook info field shape (CANONICAL DUMP).
+ *
+ * A single step named {@code "greet"} returning the constant {@code "task-a"}. The instrumentation plugin (filtering
+ * to step-type operations) emits ONE single-line JSON record per operation hook event: a canonical dump of that hook's
+ * OWN info parameter, every exposed component mapped to its canonical camelCase name, null / unexposed fields OMITTED.
+ *
+ *
Java's {@link OperationInfo} (operation-start) exposes id/name/type/subType/parentId/startTimestamp/endTimestamp/
+ * status/isReplay; at a LIVE first start {@code status}/{@code startTimestamp} may be unset and are simply omitted, and
+ * the record has no {@code attempt}/{@code result}/{@code error} at all. Java's {@link OperationEndInfo}
+ * (operation-end) adds {@code attempt} and {@code error} but does NOT expose the checkpointed serialized result, so
+ * {@code result} is absent on the end record — that omission is the honest red the requirement produces.
+ */
+@SuppressWarnings("deprecation")
+public class PluginOperationInfoShape extends DurableHandler {
+
+ @Override
+ protected DurableConfig createConfiguration() {
+ return DurableConfig.builder().withPlugins(new OperationShapePlugin()).build();
+ }
+
+ @Override
+ public String handleRequest(Object input, DurableContext context) {
+ return context.step("greet", String.class, stepCtx -> "task-a");
+ }
+
+ private static final class OperationShapePlugin implements DurableExecutionPlugin {
+ private volatile String executionArn;
+
+ @Override
+ public void onInvocationStart(InvocationInfo info) {
+ this.executionArn = info.durableExecutionArn();
+ }
+
+ @Override
+ public void onOperationStart(OperationInfo info) {
+ if (!PluginSupport.isStep(info.type())) {
+ return;
+ }
+ new Rec("operation-start")
+ .str("id", info.id())
+ .str("name", info.name())
+ .str("type", Rec.upper(info.type()))
+ .str("subType", info.subType())
+ .str("parentId", info.parentId())
+ .str("status", Rec.upper(info.status()))
+ .time("startTimestamp", info.startTimestamp())
+ .time("endTimestamp", info.endTimestamp())
+ .bool("isReplay", info.isReplay())
+ .emit(executionArn);
+ }
+
+ @Override
+ public void onOperationEnd(OperationEndInfo info) {
+ if (!PluginSupport.isStep(info.type())) {
+ return;
+ }
+ new Rec("operation-end")
+ .str("id", info.id())
+ .str("name", info.name())
+ .str("type", Rec.upper(info.type()))
+ .str("subType", info.subType())
+ .str("parentId", info.parentId())
+ .str("status", Rec.upper(info.status()))
+ .time("startTimestamp", info.startTimestamp())
+ .time("endTimestamp", info.endTimestamp())
+ .num("attempt", info.attempt())
+ .bool("isReplay", info.isReplay())
+ .str("error", Rec.msg(info.error()))
+ .emit(executionArn);
+ }
+ }
+
+ /** Single-line JSON record builder: emits every provided key, skipping nulls, then stamps durableExecutionArn. */
+ private static final class Rec {
+ private final StringBuilder sb = new StringBuilder("{");
+
+ Rec(String hook) {
+ raw("plugin", "\"CONFPLUGIN\"");
+ raw("hook", "\"" + hook + "\"");
+ }
+
+ Rec str(String key, String value) {
+ if (value != null) {
+ raw(key, quote(value));
+ }
+ return this;
+ }
+
+ Rec num(String key, Integer value) {
+ if (value != null) {
+ raw(key, value.toString());
+ }
+ return this;
+ }
+
+ Rec bool(String key, boolean value) {
+ raw(key, value ? "true" : "false");
+ return this;
+ }
+
+ Rec time(String key, Instant value) {
+ if (value != null) {
+ raw(key, quote(value.toString()));
+ }
+ return this;
+ }
+
+ private void raw(String key, String jsonValue) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append('"').append(key).append("\": ").append(jsonValue);
+ }
+
+ void emit(String executionArn) {
+ System.out.println(sb.append(PluginSupport.arnField(executionArn)).append('}'));
+ }
+
+ static String upper(String s) {
+ return s == null ? null : s.toUpperCase(Locale.ROOT);
+ }
+
+ static String msg(Throwable t) {
+ if (t == null) {
+ return null;
+ }
+ return t.getMessage() != null ? t.getMessage() : t.toString();
+ }
+
+ static String quote(String s) {
+ StringBuilder b = new StringBuilder("\"");
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '"':
+ b.append("\\\"");
+ break;
+ case '\\':
+ b.append("\\\\");
+ break;
+ case '\n':
+ b.append("\\n");
+ break;
+ case '\r':
+ b.append("\\r");
+ break;
+ case '\t':
+ b.append("\\t");
+ break;
+ default:
+ if (c < 0x20) {
+ b.append(String.format("\\u%04x", (int) c));
+ } else {
+ b.append(c);
+ }
+ }
+ }
+ return b.append('"').toString();
+ }
+ }
+}
diff --git a/conformance-tests/template_plugin.yaml b/conformance-tests/template_plugin.yaml
index ecc8cac17..ebc680135 100644
--- a/conformance-tests/template_plugin.yaml
+++ b/conformance-tests/template_plugin.yaml
@@ -304,3 +304,67 @@ Resources:
DurableConfig:
RetentionPeriodInDays: 7
ExecutionTimeout: 300
+
+ PluginInvocationInfoShape:
+ Type: AWS::Serverless::Function
+ TestingMetadata:
+ TestDescription: ["10-19"]
+ Properties:
+ CodeUri: .
+ Handler: plugin.PluginInvocationInfoShape
+ Description: Invocation-start and invocation-end hook info carries the full invocation field set
+ Role:
+ Fn::GetAtt:
+ - DurableFunctionRole
+ - Arn
+ DurableConfig:
+ RetentionPeriodInDays: 7
+ ExecutionTimeout: 300
+
+ PluginOperationInfoShape:
+ Type: AWS::Serverless::Function
+ TestingMetadata:
+ TestDescription: ["10-20"]
+ Properties:
+ CodeUri: .
+ Handler: plugin.PluginOperationInfoShape
+ Description: Operation-start and operation-end hook info carries the full operation field set
+ Role:
+ Fn::GetAtt:
+ - DurableFunctionRole
+ - Arn
+ DurableConfig:
+ RetentionPeriodInDays: 7
+ ExecutionTimeout: 300
+
+ PluginAttemptInfoShape:
+ Type: AWS::Serverless::Function
+ TestingMetadata:
+ TestDescription: ["10-21"]
+ Properties:
+ CodeUri: .
+ Handler: plugin.PluginAttemptInfoShape
+ Description: Attempt-start and attempt-end hook info carries the full attempt field set
+ Role:
+ Fn::GetAtt:
+ - DurableFunctionRole
+ - Arn
+ DurableConfig:
+ RetentionPeriodInDays: 7
+ ExecutionTimeout: 300
+
+ PluginOperationChangeShape:
+ Type: AWS::Serverless::Function
+ TestingMetadata:
+ TestDescription: ["10-22"]
+ Properties:
+ CodeUri: .
+ Handler: plugin.PluginOperationChangeShape
+ Description: Operation-change hook info carries full operation items in the delta and full map
+ Role:
+ Fn::GetAtt:
+ - DurableFunctionRole
+ - Arn
+ DurableConfig:
+ RetentionPeriodInDays: 7
+ ExecutionTimeout: 300