diff --git a/conformance/parallel-groups/README.md b/conformance/parallel-groups/README.md new file mode 100644 index 0000000..202c4a4 --- /dev/null +++ b/conformance/parallel-groups/README.md @@ -0,0 +1,152 @@ +# Parallel group failure policy, version 1 + +Proposal for [issue 232](https://github.com/durable-workflow/.github/issues/232). +Workflow acknowledged sdk-python's handoff for this contract and fixture slice. +Workflow retains embedded producer admission, Server retains native admission, +and the first-party bindings retain authoring and replay. + +This document defines a proposed wire contract. It does not advertise runtime +support or qualify a release. Producers and consumers require review and +published-artifact qualification before enabling it. + +## Recorded discriminator + +Each canonical group metadata entry may contain `parallel_group_policy` with +exactly one of `propagate`, `cancel_remaining` or `settle`. It appears in every +applicable frame of `parallel_group_path`; the top-level flattened fields match +the innermost frame, as the other group fields do today. + +The policy belongs to a group identity and is immutable after admission. Every +member's frame for that group must agree, including presence versus absence. +An unknown, null, empty or inconsistent value is refused before writes. +Policy is refused on selection frames (`parallel_group_mode: select`). An +enclosing ordinary all-group frame may carry its own policy; that does not +change the selection frame's winner/handle contract. + +Absence preserves the recording binding's legacy behavior, including failure +selection and replay clock. In particular, legacy Python list groups wait for +every member and select the first failure in member order. A replacement worker +must not reinterpret that history as explicit `propagate`. + +Newly admitted version-1 groups record their chosen policy, including the default +`propagate`. Replay uses the recorded policy, not a new worker's default. An +already-open legacy group keeps absence even when replayed by a newer worker. + +## Capability admission + +The proposed discovery field is +`worker_protocol.server_capabilities.parallel_group_policies`: + +```json +{"supported": true, "version": 1, "policies": ["propagate"]} +``` + +`policies` is a nonempty unique subset of the three policy names. Producers may +admit one policy before the others, and list only policies actually implemented +and qualified. This descriptor is a closed object with exactly `supported`, +`version` and `policies`. `supported` must be the boolean `true`, `version` must +be the integer `1`, and every policy must be a string. Unknown members, wrong +types (including boolean or string versions), unknown versions or names, +duplicates, missing fields, an empty policy list and `supported: false` refuse +explicit admission. No value is coerced or silently discarded. The same closed +descriptor rules apply to discovery and worker registration. Workers without +support omit the registration flag and descriptor. + +Worker registration uses the existing `POST /worker/register` body fields: + +```json +{ + "capabilities": ["parallel_group_policies"], + "capability_manifest": { + "parallel_group_policies": {"supported": true, "version": 1, "policies": ["propagate"]} + } +} +``` + +The structured descriptor is exactly at +`capability_manifest.parallel_group_policies`, separate from the existing flat +string array `capabilities`. The flag and descriptor must both be present. +Neither alone establishes policy support. A legacy worker omits both. An object +inside `capabilities`, a misplaced descriptor, an invalid descriptor, or a +flag/descriptor mismatch refuses registration before normalization or writes. +Other existing capability entries retain their contracts. + +The producer validates and persists this entry as structured metadata. It must +not discard the descriptor during registration normalization or route it through +the flat string-list normalizer. It validates exact version and policy names, rejects +duplicates, and canonicalizes the policy list in `propagate`, `cancel_remaining`, +`settle` order. The canonical descriptor participates in registration identity, +held-poll snapshots and in-transaction claim/confirmation fences. A descriptor +change during a held poll makes that old poll stale, even if its flat flag is +unchanged. It cannot become the replacement process's claim authority. + +Producer discovery alone does not establish consumer support. Admission requires +both sides to support the selected policy. A worker must not advertise a policy +implemented only by a test flag or source proposal. + +The same fence applies to replacement-worker claim and replay eligibility. +Before assigning or confirming a claim, the producer verifies that the worker +supports every explicit policy needed by that run's retained history. A legacy +worker must not claim an explicit-policy run merely because it supports the +workflow type. If no eligible worker exists, keep the run waiting with an +operator-visible capability reason. Never strip recorded policy or downgrade +it during repair, registration replacement, retry or cold replay. Completion +also retains the existing current-worker, lease and attempt fences. + +If either side lacks the selected policy, refuse explicit admission before +recording commands. Never drop the policy, silently downgrade it to legacy +behavior or partially admit the member batch. Existing absent-policy histories +remain replayable under their original semantics. + +## Outcomes and replay boundaries + +`propagate` raises the first member failure in durable history order. It does +not wait for open siblings or issue sibling cancellation. Successful groups +still return values in member order after every member succeeds. A later +sibling result cannot become an outcome for a successor authored call. + +`cancel_remaining` uses the same first-failure boundary and requests cancellation +for unfinished siblings in member order before raising the initiating failure. +It preserves existing operation cancellation policies, ownership and deadlines. +Cancellation is recorded and replayed; replay never sends another request for +the same recorded cancellation. This policy needs a producer-reviewed mapping +to existing operation cancellation commands before it can be advertised. The +fixture specifies required targets, not a new unreviewed command kind. + +`settle` waits for every member and returns an ordered outcome per member. Each +outcome distinguishes success (including a null value) from failure; a member +failure is not thrown at the group call. The language-neutral outcome envelope +requires binding/producer review before this policy is advertised. + +First-failure ties follow durable event order, never member position or a sort +of timestamps. The explicit-policy failure continuation receives only the +selected failure's qualified event clock. Later sibling results and later +signals cannot supply that boundary's time. Absent-policy histories keep their +binding's original clock rule. A terminal run-close settlement is not a member +outcome, as defined by the existing run-close contract. + +Activity-group failure clocks keep the established canonical-v1 millisecond +normalization. If resource identity or timestamp checks cannot prove the selected +boundary, the clock remains unavailable rather than borrowing another event. + +Nested groups record policy per group frame. Initial consumers may support only +uniform explicit `propagate` frames; mixed nested policies must remain refused +until their outcome composition is qualified. Local/prepared operations need +their own atomic admission support and must not inherit support merely because +remote activities work. + +## Fixtures and remaining decisions + +[`fixtures-v1.json`](fixtures-v1.json) contains synthetic canonical history +prefixes and expected semantic outcomes. Binding owners copy applicable inputs +into their existing replay suites and retain legacy fixtures unchanged. These +source vectors are not an installed conformance result. +Registration/claim vectors separately cover the exact manifest placement, +legacy eligibility, malformed or incomplete declarations, retained-policy +requirements and a capability change during a held poll. + +The propagate wire discriminator and proposed per-policy capability can be +reviewed independently of the producer mappings for `cancel_remaining` and +`settle`. Those two policies stay unadvertised until their cancellation command +mapping and ordered outcome envelope are agreed and implemented. No runtime +change or release is authorized by a passing fixture-only check. diff --git a/conformance/parallel-groups/fixtures-v1.json b/conformance/parallel-groups/fixtures-v1.json new file mode 100644 index 0000000..58e2be7 --- /dev/null +++ b/conformance/parallel-groups/fixtures-v1.json @@ -0,0 +1,1731 @@ +{ + "schema": "durable-workflow.parallel-group-policy-fixtures/v1", + "status": "proposal-not-qualified", + "capability": { + "discovery_path": "worker_protocol.server_capabilities.parallel_group_policies", + "descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + }, + "registration_path": "capability_manifest.parallel_group_policies", + "registration_flag": "parallel_group_policies" + }, + "cases": [ + { + "id": "legacy-python-pending", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0 + } + ] + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1 + } + ] + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1 + } + ], + "message": "higher position failed first" + }, + "sequence": 4 + } + ], + "expected": { + "continuation": "pending", + "failure": null, + "open_member_indexes": [ + 0 + ] + }, + "legacy_binding": "python" + }, + { + "id": "propagate-first-recorded-failure", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "propagate" + }, + "sequence": 4 + } + ], + "expected": { + "continuation": "failed", + "failure_member_index": 1, + "cancellation_requests": [], + "open_member_indexes": [ + 0 + ], + "failure_event_sequence": 4, + "clock_event_sequence": 4 + } + }, + { + "id": "cancel-remaining-targets-open-sibling", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "cancel_remaining" + } + ], + "parallel_group_policy": "cancel_remaining" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "cancel_remaining" + } + ], + "parallel_group_policy": "cancel_remaining" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "cancel_remaining" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "cancel_remaining" + }, + "sequence": 4 + } + ], + "expected": { + "continuation": "failed", + "failure_member_index": 1, + "cancellation_requests": [ + { + "member_index": 0, + "sequence": 1 + } + ] + } + }, + { + "id": "settle-waits-for-open-sibling", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "settle" + } + ], + "parallel_group_policy": "settle" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "settle" + } + ], + "parallel_group_policy": "settle" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "settle" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "settle" + }, + "sequence": 4 + } + ], + "expected": { + "continuation": "pending", + "open_member_indexes": [ + 0 + ] + } + }, + { + "id": "legacy-python-positional-failure", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0 + } + ] + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1 + } + ] + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1 + } + ], + "message": "higher position failed first" + }, + "sequence": 4 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:03Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0 + } + ], + "message": "lower position failed later" + }, + "sequence": 5 + } + ], + "expected": { + "continuation": "failed", + "failure_member_index": 0 + }, + "legacy_binding": "python" + }, + { + "id": "propagate-preserves-first-after-late-loser", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "propagate" + }, + "sequence": 4 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:03Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "message": "lower position failed later", + "parallel_group_policy": "propagate" + }, + "sequence": 5 + } + ], + "expected": { + "continuation": "failed", + "failure_member_index": 1, + "failure_event_sequence": 4, + "clock_event_sequence": 4 + } + }, + { + "id": "settle-orders-failures-by-member", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "settle" + } + ], + "parallel_group_policy": "settle" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "settle" + } + ], + "parallel_group_policy": "settle" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "settle" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "settle" + }, + "sequence": 4 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:03Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "settle" + } + ], + "message": "lower position failed later", + "parallel_group_policy": "settle" + }, + "sequence": 5 + } + ], + "expected": { + "continuation": "completed", + "outcomes": [ + { + "member_index": 0, + "status": "failed" + }, + { + "member_index": 1, + "status": "failed" + } + ] + } + }, + { + "id": "propagate-tied-failures-follow-recorded-order", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "propagate" + }, + "sequence": 4 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "message": "lower position failed later", + "parallel_group_policy": "propagate" + }, + "sequence": 5 + } + ], + "expected": { + "continuation": "failed", + "failure_member_index": 1, + "failure_event_sequence": 4, + "clock_event_sequence": 4 + } + }, + { + "id": "propagate-late-sibling-does-not-supply-successor-clock", + "history": [ + { + "event_type": "WorkflowStarted", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": {}, + "sequence": 1 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 2 + }, + { + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:00Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate" + }, + "sequence": 3 + }, + { + "event_type": "ActivityFailed", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 2, + "activity_execution_id": "execution-1", + "activity_type": "fast-second", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 1, + "parallel_group_policy": "propagate" + } + ], + "message": "higher position failed first", + "parallel_group_policy": "propagate" + }, + "sequence": 4 + }, + { + "sequence": 5, + "event_type": "ActivityScheduled", + "recorded_at": "2026-01-01T00:00:01Z", + "payload": { + "sequence": 3, + "activity_type": "after-group", + "activity_execution_id": "tail" + } + }, + { + "sequence": 6, + "event_type": "ActivityCompleted", + "recorded_at": "2026-01-01T00:00:02Z", + "payload": { + "sequence": 3, + "activity_type": "after-group", + "activity_execution_id": "tail", + "result": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwoWdGFpbC1yZXN1bHQ=" + } + } + }, + { + "sequence": 7, + "event_type": "ActivityCompleted", + "recorded_at": "2026-01-01T00:00:09Z", + "payload": { + "sequence": 1, + "activity_execution_id": "execution-0", + "activity_type": "slow-first", + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_path": [ + { + "parallel_group_id": "parallel-activities:1:2", + "parallel_group_kind": "activity", + "parallel_group_base_sequence": 1, + "parallel_group_size": 2, + "parallel_group_index": 0, + "parallel_group_policy": "propagate" + } + ], + "parallel_group_policy": "propagate", + "result": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwoUbGF0ZS1sb3Nlcg==" + } + } + } + ], + "expected": { + "continuation": "failed", + "failure_member_index": 1, + "cancellation_requests": [], + "open_member_indexes": [ + 0 + ], + "failure_event_sequence": 4, + "clock_event_sequence": 4, + "successor_result": "tail-result", + "successor_clock_event_sequence": 6 + }, + "successor": { + "sequence": 3, + "activity_type": "after-group" + } + } + ], + "refusal_vectors": [ + { + "id": "unknown-policy", + "policy": "best_effort" + }, + { + "id": "null-policy", + "policy": null + }, + { + "id": "inconsistent-member-policy", + "member_policies": [ + "propagate", + "settle" + ] + }, + { + "id": "missing-producer-capability", + "producer_capability": null, + "policy": "propagate" + }, + { + "id": "unsupported-selected-policy", + "producer_capability": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + }, + "policy": "settle" + }, + { + "id": "legacy-replacement-worker", + "recorded_policy": "propagate", + "replacement_worker_capability": null + }, + { + "id": "policy-on-select-frame", + "parallel_group_mode": "select", + "parallel_group_policy": "propagate" + } + ], + "registration_claim_vectors": [ + { + "id": "legacy-registration-legacy-run", + "registration": { + "capabilities": [], + "capability_manifest": {} + }, + "run_required_policies": [], + "expected": { + "registration": "accepted", + "claim": "eligible" + } + }, + { + "id": "qualified-propagate-registration", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "producer_descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + }, + "run_required_policies": [ + "propagate" + ], + "expected": { + "registration": "accepted", + "stored_descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + }, + "claim": "eligible" + } + }, + { + "id": "flat-flag-without-descriptor", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": {} + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "descriptor-without-flat-flag", + "registration": { + "capabilities": [], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "object-in-flat-capabilities", + "registration": { + "capabilities": [ + { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + } + } + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "descriptor-at-wrong-registration-path", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + }, + "capability_manifest": {} + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "duplicate-registration-policies", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "future-registration-version", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 2, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "legacy-worker-cannot-claim-explicit-run", + "registration": { + "capabilities": [], + "capability_manifest": {} + }, + "run_required_policies": [ + "propagate" + ], + "expected": { + "registration": "accepted", + "claim": "ineligible", + "run": "waiting" + } + }, + { + "id": "settled-group-retains-policy-requirement", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "run_required_policies": [ + "propagate", + "settle" + ], + "open_group_policies": [], + "expected": { + "registration": "accepted", + "claim": "ineligible", + "run": "waiting" + }, + "producer_descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + "settle" + ] + } + }, + { + "id": "descriptor-change-invalidates-held-poll", + "held_poll_registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + "settle" + ] + } + } + }, + "replacement_registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "producer_descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + "settle" + ] + }, + "run_required_policies": [ + "propagate" + ], + "expected": { + "registration": "accepted", + "held_poll_claim": "stale_worker_registration", + "replacement_claim": "eligible" + } + }, + { + "id": "policy-order-canonicalization", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "settle", + "propagate" + ] + } + } + }, + "producer_descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + "settle" + ] + }, + "run_required_policies": [ + "settle" + ], + "expected": { + "registration": "accepted", + "stored_descriptor": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + "settle" + ] + }, + "claim": "eligible" + } + }, + { + "id": "null-registration-descriptor", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": null + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-policy-list-wrong-type", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": "propagate" + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "unknown-registration-descriptor-member", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate" + ], + "allow_legacy": true + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-supported-false", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": false, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-supported-string", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": "true", + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-supported-number", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": 1, + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-version-boolean", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": true, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-version-string", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": "1", + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-version-fractional-number", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1.0, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-policies-empty", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-policy-unknown", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "future" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-policy-member-wrong-type", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1, + "policies": [ + "propagate", + 1 + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-descriptor-wrong-object-type", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": [] + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-missing-supported", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "version": 1, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-missing-version", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "policies": [ + "propagate" + ] + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + }, + { + "id": "registration-missing-policies", + "registration": { + "capabilities": [ + "parallel_group_policies" + ], + "capability_manifest": { + "parallel_group_policies": { + "supported": true, + "version": 1 + } + } + }, + "expected": { + "registration": "refused", + "writes": false + } + } + ] +}