From 8188ba772b1c9e2b47e2ce51ae528149867f0f09 Mon Sep 17 00:00:00 2001 From: wangbill Date: Tue, 6 Oct 2026 17:38:06 -0400 Subject: [PATCH 1/2] Add tags to entity-scheduled orchestration actions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3d59e0dc-efdb-4b0a-b588-6dbd83416193 --- README.md | 7 ++ .../StartNewOrchestrationActionTest.java | 102 ++++++++++++++++++ protos/orchestrator_service.proto | 1 + 3 files changed, 110 insertions(+) create mode 100644 lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java diff --git a/README.md b/README.md index 52c5b4e..d4e072c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ This repo contains [protocol buffer](https://developers.google.com/protocol-buff used by the Durable Task framework sidecar architecture. It's recommended that Durable Task language SDKs reference the protobuf contracts in this repo via [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). +## Entity-scheduled orchestration tags + +`StartNewOrchestrationAction.tags` is a `map` at field 8. Omitting tags or sending an +empty map preserves the existing wire representation. Older receivers do not expose this field and +may discard it. SDKs must send the tags and receiving services must consume them to persist them or +make them searchable; the protocol definition alone does not enable scheduler tag search. + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java b/lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java new file mode 100644 index 0000000..db4cb16 --- /dev/null +++ b/lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.durabletask.implementation.protobuf; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.Message; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class StartNewOrchestrationActionTest { + @Test + public void tagsUseStringMapFieldEight() { + FieldDescriptor tags = tagsField(); + assertEquals(8, tags.getNumber()); + assertTrue(tags.isMapField()); + assertNull(tags.getContainingOneof()); + Descriptor entry = tags.getMessageType(); + assertEquals("key", entry.findFieldByNumber(1).getName()); + assertEquals(FieldDescriptor.Type.STRING, entry.findFieldByNumber(1).getType()); + assertEquals("value", entry.findFieldByNumber(2).getName()); + assertEquals(FieldDescriptor.Type.STRING, entry.findFieldByNumber(2).getType()); + } + + @Test + public void emptyTagsRetainTheUntaggedWireFormat() { + FieldDescriptor tags = tagsField(); + OrchestratorService.StartNewOrchestrationAction action = + OrchestratorService.StartNewOrchestrationAction.newBuilder() + .setInstanceId("id") + .setName("name") + .setField(tags, Collections.emptyList()) + .build(); + assertArrayEquals( + new byte[] {10, 2, 'i', 'd', 18, 4, 'n', 'a', 'm', 'e'}, + action.toByteArray()); + assertArrayEquals( + new byte[0], + OrchestratorService.StartNewOrchestrationAction.getDefaultInstance().toByteArray()); + } + + @Test + public void tagsRoundTripThroughTheEntityOperationEnvelope() throws Exception { + FieldDescriptor tags = tagsField(); + Descriptor entry = tags.getMessageType(); + OrchestratorService.StartNewOrchestrationAction action = + OrchestratorService.StartNewOrchestrationAction.newBuilder() + .setInstanceId("id") + .setName("name") + .addRepeatedField(tags, tagEntry(entry, "tenant", "example")) + .addRepeatedField(tags, tagEntry(entry, "empty", "")) + .build(); + OrchestratorService.EntityBatchResult batch = + OrchestratorService.EntityBatchResult.newBuilder() + .addActions(OrchestratorService.OperationAction.newBuilder() + .setId(42) + .setStartNewOrchestration(action)) + .build(); + OrchestratorService.EntityBatchResult parsed = + OrchestratorService.EntityBatchResult.parseFrom(batch.toByteArray()); + assertEquals(batch, parsed); + assertEquals(42, parsed.getActions(0).getId()); + assertTrue(parsed.getActions(0).hasStartNewOrchestration()); + Map actual = new HashMap<>(); + List entries = (List) parsed.getActions(0).getStartNewOrchestration().getField(tags); + for (Object value : entries) { + Message tag = (Message) value; + actual.put( + (String) tag.getField(entry.findFieldByNumber(1)), + (String) tag.getField(entry.findFieldByNumber(2))); + } + Map expected = new HashMap<>(); + expected.put("tenant", "example"); + expected.put("empty", ""); + assertEquals(expected, actual); + } + + private static FieldDescriptor tagsField() { + FieldDescriptor tags = + OrchestratorService.StartNewOrchestrationAction.getDescriptor().findFieldByName("tags"); + assertNotNull("StartNewOrchestrationAction must carry tags", tags); + return tags; + } + + private static Message tagEntry(Descriptor entry, String key, String value) { + return DynamicMessage.newBuilder(entry) + .setField(entry.findFieldByNumber(1), key) + .setField(entry.findFieldByNumber(2), value) + .build(); + } +} diff --git a/protos/orchestrator_service.proto b/protos/orchestrator_service.proto index 3d9194a..2429fff 100644 --- a/protos/orchestrator_service.proto +++ b/protos/orchestrator_service.proto @@ -706,6 +706,7 @@ message StartNewOrchestrationAction { google.protobuf.Timestamp scheduledTime = 5; google.protobuf.Timestamp requestTime = 6; TraceContext parentTraceContext = 7; + map tags = 8; } message AbandonActivityTaskRequest { From 3455f719b18ed156bbbc008d757821e72f58d63b Mon Sep 17 00:00:00 2001 From: wangbill Date: Wed, 7 Oct 2026 13:47:26 -0400 Subject: [PATCH 2/2] Remove test and documentation additions per review Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3d59e0dc-efdb-4b0a-b588-6dbd83416193 --- README.md | 7 -- .../StartNewOrchestrationActionTest.java | 102 ------------------ 2 files changed, 109 deletions(-) delete mode 100644 lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java diff --git a/README.md b/README.md index d4e072c..52c5b4e 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,6 @@ This repo contains [protocol buffer](https://developers.google.com/protocol-buff used by the Durable Task framework sidecar architecture. It's recommended that Durable Task language SDKs reference the protobuf contracts in this repo via [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). -## Entity-scheduled orchestration tags - -`StartNewOrchestrationAction.tags` is a `map` at field 8. Omitting tags or sending an -empty map preserves the existing wire representation. Older receivers do not expose this field and -may discard it. SDKs must send the tags and receiving services must consume them to persist them or -make them searchable; the protocol definition alone does not enable scheduler tag search. - ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java b/lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java deleted file mode 100644 index db4cb16..0000000 --- a/lib/java/src/test/java/com/microsoft/durabletask/implementation/protobuf/StartNewOrchestrationActionTest.java +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -package com.microsoft.durabletask.implementation.protobuf; - -import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.Message; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class StartNewOrchestrationActionTest { - @Test - public void tagsUseStringMapFieldEight() { - FieldDescriptor tags = tagsField(); - assertEquals(8, tags.getNumber()); - assertTrue(tags.isMapField()); - assertNull(tags.getContainingOneof()); - Descriptor entry = tags.getMessageType(); - assertEquals("key", entry.findFieldByNumber(1).getName()); - assertEquals(FieldDescriptor.Type.STRING, entry.findFieldByNumber(1).getType()); - assertEquals("value", entry.findFieldByNumber(2).getName()); - assertEquals(FieldDescriptor.Type.STRING, entry.findFieldByNumber(2).getType()); - } - - @Test - public void emptyTagsRetainTheUntaggedWireFormat() { - FieldDescriptor tags = tagsField(); - OrchestratorService.StartNewOrchestrationAction action = - OrchestratorService.StartNewOrchestrationAction.newBuilder() - .setInstanceId("id") - .setName("name") - .setField(tags, Collections.emptyList()) - .build(); - assertArrayEquals( - new byte[] {10, 2, 'i', 'd', 18, 4, 'n', 'a', 'm', 'e'}, - action.toByteArray()); - assertArrayEquals( - new byte[0], - OrchestratorService.StartNewOrchestrationAction.getDefaultInstance().toByteArray()); - } - - @Test - public void tagsRoundTripThroughTheEntityOperationEnvelope() throws Exception { - FieldDescriptor tags = tagsField(); - Descriptor entry = tags.getMessageType(); - OrchestratorService.StartNewOrchestrationAction action = - OrchestratorService.StartNewOrchestrationAction.newBuilder() - .setInstanceId("id") - .setName("name") - .addRepeatedField(tags, tagEntry(entry, "tenant", "example")) - .addRepeatedField(tags, tagEntry(entry, "empty", "")) - .build(); - OrchestratorService.EntityBatchResult batch = - OrchestratorService.EntityBatchResult.newBuilder() - .addActions(OrchestratorService.OperationAction.newBuilder() - .setId(42) - .setStartNewOrchestration(action)) - .build(); - OrchestratorService.EntityBatchResult parsed = - OrchestratorService.EntityBatchResult.parseFrom(batch.toByteArray()); - assertEquals(batch, parsed); - assertEquals(42, parsed.getActions(0).getId()); - assertTrue(parsed.getActions(0).hasStartNewOrchestration()); - Map actual = new HashMap<>(); - List entries = (List) parsed.getActions(0).getStartNewOrchestration().getField(tags); - for (Object value : entries) { - Message tag = (Message) value; - actual.put( - (String) tag.getField(entry.findFieldByNumber(1)), - (String) tag.getField(entry.findFieldByNumber(2))); - } - Map expected = new HashMap<>(); - expected.put("tenant", "example"); - expected.put("empty", ""); - assertEquals(expected, actual); - } - - private static FieldDescriptor tagsField() { - FieldDescriptor tags = - OrchestratorService.StartNewOrchestrationAction.getDescriptor().findFieldByName("tags"); - assertNotNull("StartNewOrchestrationAction must carry tags", tags); - return tags; - } - - private static Message tagEntry(Descriptor entry, String key, String value) { - return DynamicMessage.newBuilder(entry) - .setField(entry.findFieldByNumber(1), key) - .setField(entry.findFieldByNumber(2), value) - .build(); - } -}