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
3 changes: 2 additions & 1 deletion .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ on:
- tests/Unit/ServerParityWorkerErrorBodiesContractTest.mjs
- tests/Unit/ServerParityChildDescriptionsContractTest.mjs
- tests/Unit/ServerParityChildRetriesContractTest.mjs
- tests/Unit/ServerParityChildVisibilityContractTest.mjs
- tests/Unit/ServerParityStartSearchAttributesContractTest.mjs
- tests/Unit/ServerParityQueryArgumentsContractTest.mjs
- tests/Unit/ServerParityQueryContractArgumentsContractTest.mjs
Expand Down Expand Up @@ -1995,7 +1996,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/ServerParityRunnerBlockedExcerptTest.mjs tests/Unit/ServerParityProbeNotesTest.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/ServerParityChildDescriptionsContractTest.mjs tests/Unit/ServerParityChildRetriesContractTest.mjs tests/Unit/ServerParityWorkerErrorBodiesContractTest.mjs tests/Unit/ServerParityLargeCommandBatchesContractTest.mjs tests/Unit/ServerParityManyUpdatesContractTest.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/ServerParityProbeNotesTest.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/ServerParityChildDescriptionsContractTest.mjs tests/Unit/ServerParityChildRetriesContractTest.mjs tests/Unit/ServerParityChildVisibilityContractTest.mjs tests/Unit/ServerParityWorkerErrorBodiesContractTest.mjs tests/Unit/ServerParityLargeCommandBatchesContractTest.mjs tests/Unit/ServerParityManyUpdatesContractTest.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
4 changes: 2 additions & 2 deletions docs/rust-server-port-checkpoints/2026-10-29-child-retries.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,5 @@
delivery expects one link per call and refuses it with `409
cancellation_delivery_shape_mismatch`. Security's review confirmed this
from the source. A fix needs a PHP recording first: #873.
- Memos and search attributes inherited by child runs, retried or not
(a separate gap).
- Memos and search attributes inherited by child runs, retried or not:
the [child-visibility checkpoint](2026-10-29-child-visibility.md).
35 changes: 35 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-29-child-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# October 29, memos and search attributes on child runs

- **Before.** Native copied a run's memos and search attributes only to a
run that continued it as new. A child's first run, and a run retrying a
failed child, started with none. So their descriptions and run lists
showed an empty memo, and a visibility query for the parent's attribute
did not find the child.
- **Contract** (published Server 2.11.7, Workflow 2.15.7
`inheritTypedVisibilityMetadata`, `MemoUpsertService::inheritFromParent`
and `SearchAttributeUpsertService::inheritFromParent`):
- a child's first run takes its parent run's memos and search
attributes;
- a retry takes those of the run it retries;
- each copy is recorded as inherited at sequence 1.

A visibility query lists every run of the child that matches, the failed
first run and the pending retry alike.
- **Fixture.** `child-visibility`, raw worker HTTP on protocol 1.20 in a
fresh namespace with one registered keyword attribute. A parent started
with a memo and the attribute starts one child under a retry policy. The
probe records:
- the child's description;
- after a failure, the retry's description;
- the child's run list;
- a query for the attribute.
- **Native.** The continue-as-new copy is now two shared helpers,
`inheritable_memos` and `inherit_visibility` (`continuations.rs`).
`start_child` and the child retry call them right after creating the run.
- **Tests.** `a_child_and_its_retry_inherit_the_parents_memos_and_search_attributes`
(`child_retries.rs`), and the existing continue-as-new tests for the
shared copy.
- **Also here** (Security's re-review of #869). A child backoff sent as a
JSON object, which PHP reads as its values, is now checked at admission
like a list. Before, such an extreme value was refused only when the
retry started (`invalid_child_retry_policy`), and it never overflowed.
15 changes: 9 additions & 6 deletions docs/rust-server-port.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,7 @@ contract fixture before its correction.
(`invalid_child_timeout`, `invalid_child_retry_policy`), as a timer delay
is. Remaining: native's cooperative cancellation delivery expects one
link per call, so it refuses one delivered to a retried child
(`cancellation_delivery_shape_mismatch`; #873, pending a PHP recording);
and a retried child inherits no memos or search attributes (below).
(`cancellation_delivery_shape_mismatch`; #873, pending a PHP recording).
- **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 Expand Up @@ -654,10 +653,14 @@ contract fixture before its correction.
- **Visibility query key order.** When one command or query has several
errors, native reports them in sorted key order rather than request order.
The same holds for several invalid search attributes on one start.
- **Inherited memos and search attributes on child runs.** Published PHP
copies the parent's memos and search attributes to a child run and to a
retried child (`inheritTypedVisibilityMetadata`). Native copies both only
on continue-as-new.
- **Inherited memos and search attributes on child runs** (October 29,
closed). Published PHP copies the parent's memos and search attributes to
a child run, and the failed run's to a retried child
(`inheritTypedVisibilityMetadata`). Native copied both only on
continue-as-new; the same copy now runs for a child's first run and a
retry. The `child-visibility` fixture records it against published
2.11.7
([checkpoint](rust-server-port-checkpoints/2026-10-29-child-visibility.md)).
- **Search attribute datetime forms.** `Carbon::parse` accepts relative and
free-form dates. Native accepts RFC 3339, and a date or date and time
without an offset read as UTC, and refuses other forms as PHP refuses an
Expand Down
2 changes: 1 addition & 1 deletion rust/src/runtime/child_retries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ fn error_types(value: &Value) -> Vec<String> {
}

/// A PHP array's values: a list, or an object's values in order.
fn list(value: &Value) -> Option<Box<dyn Iterator<Item = &Value> + '_>> {
pub(super) fn list(value: &Value) -> Option<Box<dyn Iterator<Item = &Value> + '_>> {
match value {
Value::Array(entries) => Some(Box::new(entries.iter())),
Value::Object(entries) => Some(Box::new(entries.values())),
Expand Down
12 changes: 10 additions & 2 deletions rust/src/runtime/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ pub(super) fn validate_child(command: &Value) -> Result<()> {
return Err(refuse(StatusCode::UNPROCESSABLE_ENTITY, "invalid_child_timeout"));
}
}
if let Some(backoff) = command["retry_policy"]["backoff_seconds"].as_array()
&& backoff.iter().any(|entry| entry.as_i64().is_some_and(|seconds| super::child_retries::after(now, seconds).is_none()))
// PHP reads a backoff object's values as a list (`array_values`).
if let Some(mut backoff) = super::child_retries::list(&command["retry_policy"]["backoff_seconds"])
&& backoff.any(|entry| entry.as_i64().is_some_and(|seconds| super::child_retries::after(now, seconds).is_none()))
{
return Err(refuse(StatusCode::UNPROCESSABLE_ENTITY, "invalid_child_retry_policy"));
}
Expand Down Expand Up @@ -312,6 +313,10 @@ where
.bind(&child_run).bind(&child_instance).bind(namespace.as_deref()).bind(workflow_type).bind(workflow_type).bind(&arguments).bind(&queue).bind(DB::bind_time(started_at)).bind(DB::bind_time(started_at)).bind(DB::bind_time(started_at)).bind(DB::bind_time(started_at)).bind(compatibility.as_deref())
// Timeouts are at least one second; zero binds "no timeout".
.bind(run_timeout.unwrap_or(0)).bind(DB::optional_time(execution_deadline)).bind(DB::optional_time(run_deadline)).execute(&mut **tx).await?;
// `inheritTypedVisibilityMetadata`: the child takes its parent's memos
// and search attributes.
let memos = Self::inheritable_memos(tx, &parent_run).await?;
Self::inherit_visibility(tx, memos, &parent_run, &child_run, &child_instance, started_at).await?;
Self::query("INSERT INTO workflow_child_calls(parent_workflow_run_id,parent_workflow_instance_id,sequence,child_workflow_type,child_workflow_class,resolved_child_instance_id,resolved_child_run_id,parent_close_policy,cancellation_propagation,queue,status,scheduled_at,started_at,arguments,metadata,created_at,updated_at,retry_policy,timeout_policy) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,($9<>'abandon'),$10,'started',$11,$12,$13,$14,$15,$16,$17,$18)")
.bind(&parent_run).bind(&parent_instance).bind(sequence).bind(workflow_type).bind(workflow_type).bind(&child_instance).bind(&child_run).bind(&policies.parent_close).bind(&policies.cancellation).bind(&queue).bind(DB::bind_time(started_at)).bind(DB::bind_time(started_at))
.bind(DB::document(&json!({"payload":arguments}))).bind(DB::document(&json!({"child_call_id":call,"attempt_count":1,"cancellation_policy":policies.cancellation}))).bind(DB::bind_time(started_at)).bind(DB::bind_time(started_at))
Expand Down Expand Up @@ -609,6 +614,9 @@ where
.bind(&retry_run).bind(run_number).bind(run_timeout.unwrap_or(0)).bind(DB::optional_time(execution_deadline)).bind(DB::optional_time(run_deadline))
.bind(DB::bind_time(at)).bind(DB::bind_time(at)).bind(DB::bind_time(at)).bind(DB::bind_time(at)).bind(&failed_run)
.execute(&mut **tx).await?;
// The retry takes the failed run's memos and search attributes.
let memos = Self::inheritable_memos(tx, &failed_run).await?;
Self::inherit_visibility(tx, memos, &failed_run, &retry_run, &instance, at).await?;
// Each placeholder appears once, in order, as MySQL's templates require.
Self::query("UPDATE workflow_instances SET current_run_id=$1,run_count=CASE WHEN run_count>$2 THEN run_count ELSE $3 END,updated_at=$4 WHERE id=$5")
.bind(&retry_run).bind(run_number).bind(run_number).bind(DB::bind_time(at)).bind(&instance).execute(&mut **tx).await?;
Expand Down
Loading
Loading