Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ on:
- tests/Fixtures/ServerParityBaselines/**
- tests/Fixtures/ServerParityNormalization/**
- tests/Unit/ServerParityRunnerTest.mjs
- tests/Unit/ServerParityRunnerBlockedExcerptTest.mjs
- tests/Unit/ServerParitySdkDirectionLanesTest.mjs
- tests/Unit/ServerParityCodecDeadlineCellsTest.mjs
- tests/Unit/ServerParityArtifactSummaryTest.mjs
Expand Down Expand Up @@ -59,6 +60,7 @@ on:
- tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs
- tests/Unit/ServerParityConcurrentWaitsContractTest.mjs
- tests/Unit/ServerParityContinueTransfersContractTest.mjs
- tests/Unit/ServerParityLargeCommandBatchesContractTest.mjs
- tests/Unit/ServerParityStartSearchAttributesContractTest.mjs
- tests/Unit/ServerParityQueryArgumentsContractTest.mjs
- tests/Unit/ServerParityQueryContractArgumentsContractTest.mjs
Expand Down Expand Up @@ -524,7 +526,7 @@ jobs:
-e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/target \
-v "$PWD:/source:ro" -v "$PWD/cargo-home:/cargo" -v "$PWD/rust-target:/target" -w /source/rust \
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 cargo clean --locked --offline --package durable-workflow-server
- name: Bootstrap independent published PHP and native MySQL databases
- name: Bootstrap published PHP and native MySQL databases and run the native tests
run: |
set -euo pipefail
# Failed Rust tests are printed from their evidence files into this step's log.
Expand Down Expand Up @@ -1205,7 +1207,7 @@ jobs:
-e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/target \
-v "$PWD:/source:ro" -v "$PWD/cargo-home:/cargo" -v "$PWD/rust-target:/target" -w /source/rust \
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 cargo clean --locked --offline --package durable-workflow-server
- name: Bootstrap independent published PHP and native PostgreSQL databases
- name: Bootstrap published PHP and native PostgreSQL databases and run the native tests
env:
POSTGRES_IMAGE: ${{ matrix.image }}
run: |
Expand Down Expand Up @@ -1985,7 +1987,7 @@ jobs:
bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
docker run --rm --user=1000:1000 --entrypoint node -v "$PWD:/source:ro" -w /source \
"$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParitySdkDirectionLanesTest.mjs tests/Unit/ServerParityCodecDeadlineCellsTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityFailureLocationTest.mjs tests/Unit/ServerParityFailureExcerptTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs tests/Unit/ServerParitySignalsBeforeWorkerContractTest.mjs tests/Unit/ServerParityScheduleCatalogContractTest.mjs tests/Unit/ServerParityOperatorMetricsContractTest.mjs tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs tests/Unit/ServerParityConcurrentWaitsContractTest.mjs tests/Unit/ServerParityContinueTransfersContractTest.mjs tests/Unit/ServerParityStartSearchAttributesContractTest.mjs tests/Unit/ServerParityQueryArgumentsContractTest.mjs tests/Unit/ServerParityQueryContractArgumentsContractTest.mjs tests/Unit/ServerParityParentCloseCancellationContractTest.mjs tests/Unit/ServerParityRecordLocalActivityContractTest.mjs tests/Unit/ServerParityActivityCancellationAckContractTest.mjs tests/Unit/ServerParityServiceCatalogContractTest.mjs tests/Unit/ServerParityServiceEndpointsContractTest.mjs tests/Unit/ServerParityServiceCallsContractTest.mjs tests/Unit/ServerParityWorkflowImportContractTest.mjs tests/Unit/ServerParityWaterlineImportContractTest.mjs tests/Unit/ServerParitySystemMetricsContractTest.mjs tests/Unit/ServerParityOperatorDashboardTypesContractTest.mjs tests/Unit/ServerParityPrometheusMetricsContractTest.mjs tests/Unit/ServerParityPayloadCleanupContractTest.mjs tests/Unit/ServerParityRepairPassContractTest.mjs tests/Unit/ServerParityClusterInfoContractTest.mjs tests/Unit/ServerParityNamespaceDeleteContractTest.mjs tests/Unit/ServerParityMessageStreamsContractTest.mjs tests/Unit/ServerParityCancellationScopesContractTest.mjs tests/Unit/ServerParityWorkflowPollRequestContractTest.mjs tests/Unit/ServerParityUpdateValidationContractTest.mjs tests/Unit/ServerParityFixtureSelectionTest.mjs tests/Unit/ServerParityUpdateValidationRetryTest.mjs tests/Unit/ServerParityKilledAuthorPollTest.mjs tests/Unit/ServerParityImagePullTest.mjs tests/Unit/ServerParityAdapterCacheTest.mjs tests/Unit/ServerParityWorkerSessionsContractTest.mjs tests/Unit/ServerParityWorkerSessionActivitiesContractTest.mjs tests/Unit/ServerParityNexusOperationsContractTest.mjs tests/Unit/ServerParityBridgeAdaptersContractTest.mjs tests/Unit/ServerParityWorkflowStreamWritesContractTest.mjs > parity-evidence/comparator-tests.txt
"$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParityRunnerBlockedExcerptTest.mjs tests/Unit/ServerParitySdkDirectionLanesTest.mjs tests/Unit/ServerParityCodecDeadlineCellsTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityFailureLocationTest.mjs tests/Unit/ServerParityFailureExcerptTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs tests/Unit/ServerParitySignalsBeforeWorkerContractTest.mjs tests/Unit/ServerParityScheduleCatalogContractTest.mjs tests/Unit/ServerParityOperatorMetricsContractTest.mjs tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs tests/Unit/ServerParityConcurrentWaitsContractTest.mjs tests/Unit/ServerParityContinueTransfersContractTest.mjs tests/Unit/ServerParityLargeCommandBatchesContractTest.mjs tests/Unit/ServerParityStartSearchAttributesContractTest.mjs tests/Unit/ServerParityQueryArgumentsContractTest.mjs tests/Unit/ServerParityQueryContractArgumentsContractTest.mjs tests/Unit/ServerParityParentCloseCancellationContractTest.mjs tests/Unit/ServerParityRecordLocalActivityContractTest.mjs tests/Unit/ServerParityActivityCancellationAckContractTest.mjs tests/Unit/ServerParityServiceCatalogContractTest.mjs tests/Unit/ServerParityServiceEndpointsContractTest.mjs tests/Unit/ServerParityServiceCallsContractTest.mjs tests/Unit/ServerParityWorkflowImportContractTest.mjs tests/Unit/ServerParityWaterlineImportContractTest.mjs tests/Unit/ServerParitySystemMetricsContractTest.mjs tests/Unit/ServerParityOperatorDashboardTypesContractTest.mjs tests/Unit/ServerParityPrometheusMetricsContractTest.mjs tests/Unit/ServerParityPayloadCleanupContractTest.mjs tests/Unit/ServerParityRepairPassContractTest.mjs tests/Unit/ServerParityClusterInfoContractTest.mjs tests/Unit/ServerParityNamespaceDeleteContractTest.mjs tests/Unit/ServerParityMessageStreamsContractTest.mjs tests/Unit/ServerParityCancellationScopesContractTest.mjs tests/Unit/ServerParityWorkflowPollRequestContractTest.mjs tests/Unit/ServerParityUpdateValidationContractTest.mjs tests/Unit/ServerParityFixtureSelectionTest.mjs tests/Unit/ServerParityUpdateValidationRetryTest.mjs tests/Unit/ServerParityKilledAuthorPollTest.mjs tests/Unit/ServerParityImagePullTest.mjs tests/Unit/ServerParityAdapterCacheTest.mjs tests/Unit/ServerParityWorkerSessionsContractTest.mjs tests/Unit/ServerParityWorkerSessionActivitiesContractTest.mjs tests/Unit/ServerParityNexusOperationsContractTest.mjs tests/Unit/ServerParityBridgeAdaptersContractTest.mjs tests/Unit/ServerParityWorkflowStreamWritesContractTest.mjs > parity-evidence/comparator-tests.txt
docker run --rm --user=1000:1000 --cpus=1 --memory=256m --memory-swap=256m \
--network=none --entrypoint node -v "$PWD:/source:ro" -w /source \
"$image" --test tests/Unit/ServerParityCodecDeadlineLoadTest.mjs > parity-evidence/codec-load-tests.txt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# October 29, completions with more than 100 commands, and groups over 100 members

- **Before.**
- Native refused a workflow task completion with more than 100 commands
with `422 invalid_commands`. The cap was in two places: the completion
itself (`store.rs`) and the preparation of its external command
payloads (`external_resolution.rs`).
- The timer, activity and external-wait summaries gave up past 100 open
waits (`timer_summary.rs`, `activity_summary.rs`,
`external_wait_summary.rs`). A run with more than 100 was therefore
described without its wait.
- **Contract** (published 2.11.5; `WorkerController::completeWorkflowTask`,
Core 2.15.1 `WorkflowCommandNormalizer`, `StructuralLimits`):
- `commands` must be a non-empty array, with no count limit of its own.
`StructuralLimits::guardCommandBatchSize` is used only by the embedded
executor.
- Each command meets the run's structural limits instead: pending
activities and timers, 2,000 each by default.
- The run is described as waiting on its first open wait, however many
are open.
- **Fixture.** `large-command-batches`, raw worker HTTP on protocol 1.20 in
a fresh namespace. Each case completes a run's first workflow task with
150 `start_timer` or 150 `schedule_activity` commands. It records:
- the completion answer (200, run waiting, 150 created tasks);
- the run's description (`wait_kind` timer or activity, with its reason);
- the history counts by type, read in pages.

A 1,001-command case is left out: published PHP answered
`409 stale_worker_registration` for it on SQLite, so it gives no usable
reference.
- **Native.**
- Both command caps are removed; a completion must still be non-empty.
- The three summaries no longer stop at 100 open waits.
- **Parallel groups.**
- **Before.** Native capped a group's declared size at 100 members
(`all_groups.rs`). It answered any invalid group metadata with
`422 development_parallel_group_unqualified`.
- **Contract.** `WorkflowCommandNormalizer::preflightParallelMetadata`
runs in `WorkerController` after the transport rules and integer
normalization, before the controller's scopes and any ownership guard.
It casts digit strings in the parallel integer fields, refuses parallel
fields on a non-leaf command, and checks each leaf's metadata
(`optionalParallelMetadata`). It collects errors across the batch, with
one of three messages. A group's declared size is bounded by
`StructuralLimits::commandBatchSizeLimit` (1,000 by default).
- **Fixture.** An all group of 150 activities, each with a one-entry
`parallel_group_path`, is accepted like the plain batch. A group
declaring 1,001 members (two commands) is refused
`422 validation_failed`, with an error on each command, and records
nothing.
- **Native.** `completion_rules.rs` ports the preflight at the same point
in `checked`. The batch-size limit is one shared constant
(`memo_limits::COMMAND_BATCH_SIZE`), used by the group, cooperative
cancellation and cancellation scope checks. A valid group that native
does not run (for example a child group) is still refused later with
`development_parallel_group_unqualified`.
- **Integer range (from review).** PHP's member-range sums past the
integer range become floats that fail its bounds, and a numeric member
key above it decodes to a float. Native uses checked sums and the signed
64-bit range for numeric keys; the earlier native sum overflowed.
- **Tests.** `large_command_batches.rs` (150 timers and 150 activities
accepted, the run described as waiting on its wait; a 150-member group
accepted and a 1,001-member group refused on both commands; a select
member past the integer range and a key above it refused by the
preflight). All fail on the previous source.
23 changes: 20 additions & 3 deletions docs/rust-server-port.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,12 @@ contract fixture before its correction.
of the Workflow package's `DW_V2_LIMIT_*` limits: the memo size, the
command batch size as a bound on parallel group metadata, the pending
signal and update counts with their payload size at intake, and the
payload size of an activity's result. Native enforces all but the command
batch size, and still caps parallel groups at 100 members and a run at 64
updates over its lifetime. #612 tracks the rest
payload size of an activity's result. Native enforces all four; the
command batch size is fixed at its default of 1,000, since native refuses
to start with another value. A completion itself has no command-count
limit in either (October 29: native's 100-command and 100-member group
caps are removed; the 64-update lifetime cap went on October 23). #612
tracks the rest
([checkpoint](rust-server-port-checkpoints/2026-10-23-structural-limits.md)).
- **Completion budget, ahead of published PHP.** Native applies Phase A of
[Workflow #930](https://github.com/durable-workflow/workflow/issues/930)
Expand Down Expand Up @@ -561,6 +564,20 @@ contract fixture before its correction.
fixture records twenty-two cases against
published 2.11.5
([checkpoint](rust-server-port-checkpoints/2026-10-28-query-contract-arguments.md)).
- **Completions with more than 100 commands** (October 29, closed).
Published PHP accepts any non-empty workflow task completion from an
external worker; each command meets the run's structural limits (pending
activities and timers) instead. Native refused more than 100 commands
with `invalid_commands`. Its timer, activity and external-wait summaries
also stopped at 100 open waits, so a run with more than 100 was described
without its wait. Native also capped a parallel group at 100 members,
where PHP bounds it by the command batch size (1,000). It refused an
invalid group as `development_parallel_group_unqualified`, where PHP's
preflight answers `validation_failed` with an error on each command. All
of these are removed or ported. The `large-command-batches` fixture records
150 timers, 150 activities, a 150-member group and a 1,001-member group
against published 2.11.5
([checkpoint](rust-server-port-checkpoints/2026-10-29-large-command-batches.md)).
- **Trace context (Core 2.10, published in Server 2.11.1).** Core adds
nullable `observability_context` columns on runs, commands and tasks.
Native has no carrier: it accepts and ignores a top-level carrier on
Expand Down
7 changes: 3 additions & 4 deletions rust/src/runtime/activity_summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ impl ActivitySummaryHistory {
},
};
if event == "ActivityScheduled" {
if self.open.len() >= 100
|| self.open.contains_key(id)
if self.open.contains_key(id)
|| attempts != 0
|| next.attempt_id.is_some()
{
Expand Down Expand Up @@ -213,9 +212,9 @@ where
history: &ActivitySummaryHistory,
summary: &mut ActiveSummary,
) -> Result<bool> {
let rows = Self::query("SELECT w.*,a.id AS activity_id,a.sequence AS activity_sequence,a.activity_type,a.status AS activity_status,a.attempt_count,a.current_attempt_id,a.parallel_group_path AS activity_group_path,q.id AS matched_task_id,q.task_type AS matched_task_type,q.payload AS matched_task_payload FROM workflow_run_waits w LEFT JOIN activity_executions a ON a.id=w.resume_source_id AND a.workflow_run_id=w.workflow_run_id LEFT JOIN workflow_tasks q ON q.id=w.task_id AND q.workflow_run_id=w.workflow_run_id WHERE w.workflow_run_id=$1 AND w.kind='activity' AND w.status='open' ORDER BY w.sequence,w.opened_at,w.resume_source_id LIMIT 101")
let rows = Self::query("SELECT w.*,a.id AS activity_id,a.sequence AS activity_sequence,a.activity_type,a.status AS activity_status,a.attempt_count,a.current_attempt_id,a.parallel_group_path AS activity_group_path,q.id AS matched_task_id,q.task_type AS matched_task_type,q.payload AS matched_task_payload FROM workflow_run_waits w LEFT JOIN activity_executions a ON a.id=w.resume_source_id AND a.workflow_run_id=w.workflow_run_id LEFT JOIN workflow_tasks q ON q.id=w.task_id AND q.workflow_run_id=w.workflow_run_id WHERE w.workflow_run_id=$1 AND w.kind='activity' AND w.status='open' ORDER BY w.sequence,w.opened_at,w.resume_source_id")
.bind(run_id).fetch_all(&mut **tx).await?;
if rows.len() != history.open.len() || rows.len() > 100 || !history.local.is_empty() {
if rows.len() != history.open.len() || !history.local.is_empty() {
return Ok(false);
}
let mut selected_activity_task = false;
Expand Down
2 changes: 1 addition & 1 deletion rust/src/runtime/all_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn entry(value: &Value, leaf: &str) -> Result<Value> {
.ok_or_else(invalid)?;
let size = value["parallel_group_size"]
.as_i64()
.filter(|size| (1..=100).contains(size))
.filter(|size| (1..=super::memo_limits::COMMAND_BATCH_SIZE).contains(size))
.ok_or_else(invalid)?;
let index = value["parallel_group_index"]
.as_i64()
Expand Down
3 changes: 1 addition & 2 deletions rust/src/runtime/cancellation_scopes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ const DELIVERY_SCHEMA: &str = "durable-workflow.cancellation-scope-delivery/v1";
const SCOPED_CONTEXT_SCHEMA: &str = "durable-workflow.scoped-cancellation-context/v1";
const ACTIVITY_SCOPE_SCHEMA: &str = "durable-workflow.activity-scope-cancellation/v1";
const TIMER_SCOPE_SCHEMA: &str = "durable-workflow.scoped-timer-cancellation/v1";
/// PHP's default `command_batch_size` structural limit.
const SPAN_LIMIT: i64 = 1000;
use super::memo_limits::COMMAND_BATCH_SIZE as SPAN_LIMIT;
/// The command types whose `cancellation_scope_id` records membership.
const MEMBER_COMMANDS: &[&str] = &["schedule_activity", "start_timer", "start_child_workflow", "open_signal_wait", "open_condition_wait"];
/// The command types the admission guard lets carry a scope.
Expand Down
Loading
Loading