Skip to content

Commit 5f6832d

Browse files
rmcdanielclaude
andcommitted
Rust #325: validate query arguments against their durable contract
Published Server checks a declared query's arguments with Workflow's WorkflowQueryContract::validatedArgumentsForRun: a string-keyed map is taken as named arguments, positional ones are matched in order, defaults are filled in and variadics collected, and each violation is reported with its own message under validation_errors ("argument validation failed"). The worker receives the normalized positional list, encoded again. A declared query without a parameter contract takes a list as given and refuses named arguments. Native checked positional arguments only, refused a map, reported every violation as a bare invalid_query_arguments and refused some contracts as unsupported. query_contract.rs ports the normalization and PHP's declared type rules; the query-contract-arguments fixture records twelve cases against published Server 2.11.5. Found in review of #849. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 85664b2 commit 5f6832d

15 files changed

Lines changed: 1027 additions & 56 deletions

‎.github/workflows/server-parity.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ on:
6161
- tests/Unit/ServerParityContinueTransfersContractTest.mjs
6262
- tests/Unit/ServerParityStartSearchAttributesContractTest.mjs
6363
- tests/Unit/ServerParityQueryArgumentsContractTest.mjs
64+
- tests/Unit/ServerParityQueryContractArgumentsContractTest.mjs
6465
- tests/Unit/ServerParityParentCloseCancellationContractTest.mjs
6566
- tests/Unit/ServerParityBridgeAdaptersContractTest.mjs
6667
- tests/Unit/ServerParityNexusOperationsContractTest.mjs
@@ -1984,7 +1985,7 @@ jobs:
19841985
bash scripts/ci/run-composer.sh --rm -v "$PWD:/source" -w /source -- \
19851986
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
19861987
docker run --rm --user=1000:1000 --entrypoint node -v "$PWD:/source:ro" -w /source \
1987-
"$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/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
1988+
"$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
19881989
docker run --rm --user=1000:1000 --cpus=1 --memory=256m --memory-swap=256m \
19891990
--network=none --entrypoint node -v "$PWD:/source:ro" -w /source \
19901991
"$image" --test tests/Unit/ServerParityCodecDeadlineLoadTest.mjs > parity-evidence/codec-load-tests.txt

‎docs/rust-server-port-checkpoints/2026-10-28-query-arguments.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
named arguments (`WorkflowQueryContract::normalizeNamedArguments`), and
2020
the worker receives the positional result. Native still refuses a map as
2121
"must decode to an array". The port log records the gap (found in
22-
review).
22+
review); the query-contract-arguments slice closes it.
2323
- **No declared queries.** The query is routed with its arguments as
2424
given. A query-capable worker leases a task carrying the blob unchanged,
2525
and the client receives the worker's answer.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# October 28, named and validated query arguments
2+
3+
- **Before.** For a declared query with a parameter contract, native
4+
differed from published PHP in four ways:
5+
- it checked positional arguments only;
6+
- it refused a named-argument map as "must decode to an array";
7+
- it answered every violation with a bare `invalid_query_arguments`;
8+
- it refused some contracts as `unsupported_query_contract` (defaults,
9+
and declared types other than a few scalars).
10+
11+
The worker then received the arguments exactly as the client sent them.
12+
- **Contract** (published 2.11.5, Core 2.15.1;
13+
`WorkflowQueryTaskBroker::validateContractedQueryArguments`,
14+
`WorkflowQueryContract::validatedArgumentsForRun`,
15+
`normalizePositionalArguments`, `normalizeNamedArguments`,
16+
`valueMatchesDeclaredType`):
17+
- **Arguments.** Arguments decode to a PHP array. A list is positional.
18+
A string-keyed map is named; keys `0` to `n - 1` cast back to a list.
19+
- **Positional.** Values fill the parameters in order. A variadic
20+
parameter takes the rest. A missing argument takes its default or is
21+
reported if required. Extra values are reported under `arguments`.
22+
- **Named.** Each parameter takes its name's value. A variadic parameter
23+
spreads a list or a map's values. Defaults and required arguments are
24+
handled as for positional ones. Each unknown name is reported under
25+
itself.
26+
- **Values.** A null is refused unless the parameter allows it. A value
27+
must match the declared type: nullable, union, intersection, `int`,
28+
`float` (an int too), `string`, `bool`, `array`, `iterable`, `scalar`,
29+
`true`, `false`, `null`, `mixed`. A class or `object` type matches only
30+
an object, which for decoded Avro is bytes.
31+
- **Refusal.** Any violation answers `422 invalid_query_arguments`, with
32+
the message "Workflow query [<name>] argument validation failed." and
33+
`validation_errors` by argument.
34+
- **Worker.** Otherwise the worker receives the normalized positional
35+
list, encoded again with the request's codec.
36+
- **No parameter contract.** A declared query without one takes a list as
37+
given and refuses named arguments: "Named arguments require a durable
38+
or loadable workflow query contract." (a native server never loads a
39+
workflow class).
40+
- **Fixture.** `query-contract-arguments`, raw HTTP on protocol 1.20 in a
41+
fresh namespace. The worker declares `state(string $prefix)`,
42+
`greet(string $prefix = 'dflt')` and `plain`. There are twelve cases:
43+
- named, positional, default and named-default queries are routed, and
44+
the worker's blob is checked;
45+
- unknown, missing, wrong-type, null, too-many and named-without-contract
46+
queries are refused, with their messages checked.
47+
- **Native.** `query_contract.rs` ports the normalization and type rules.
48+
`check_query_contract` returns the normalized list for a declared query,
49+
or the refusal. `query_attempt` enqueues the re-encoded blob.
50+
- **Kept.**
51+
- `callable` matches only an object here: PHP's `is_callable` also
52+
accepts strings naming functions, which a native server cannot resolve.
53+
- A class name that a published PHP server can load (a real class) would
54+
refuse other objects. Native, loading no classes, accepts any object.
55+
- **Tests.** `query_contract_arguments.rs` (routed normalization; refusals
56+
with messages) fails on the previous source. `query_contract.rs` unit
57+
tests cover the type rules and integer-keyed maps.

‎docs/rust-server-port.md‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -275,20 +275,6 @@ contract fixture before its correction.
275275
each priority tier across fairness keys by their weights
276276
(`TaskFairnessScheduler`); native stores the keys and weights but does not
277277
rebalance yet.
278-
- **Named and validated query arguments.** For a declared query with a
279-
durable parameter contract, published PHP normalizes the arguments through
280-
`WorkflowQueryContract::validatedArgumentsForRun`, then gives the worker the
281-
re-encoded positional result:
282-
- a string-keyed map is taken as named arguments;
283-
- positional arguments are matched to the parameters;
284-
- defaults are filled in, variadics collected, and unknown names
285-
reported;
286-
- a violation answers `422 invalid_query_arguments`, "argument validation
287-
failed", with per-parameter messages in `validation_errors`.
288-
289-
Native checks positional arguments only. It refuses a map as "must decode
290-
to an array", reports a violation as a bare `invalid_query_arguments`, and
291-
gives the worker the arguments as sent.
292278
- **When an external payload reference is claimed.** Published PHP retains
293279
a referenced object once it fetches the bytes, before the command is
294280
recorded, so a refused command still retains it. Native claims it in the
@@ -552,6 +538,26 @@ contract fixture before its correction.
552538
build-id rollout, else the single build id among the live workers for the
553539
type, else among all the queue's live workers (`start_in`,
554540
`deployments.rs`, `build_pins.rs`). The open-gap entry predated #696.
541+
- **Named and validated query arguments** (October 28, closed). For a
542+
declared query, published PHP matches the arguments to the query's durable
543+
parameter contract (`WorkflowQueryContract::validatedArgumentsForRun`):
544+
- a string-keyed map is taken as named arguments;
545+
- positional arguments are matched in order;
546+
- defaults are filled in and variadics collected;
547+
- unknown names, missing required arguments, nulls, wrong types and extra
548+
arguments are each reported. Declared types may be nullable, unions,
549+
intersections, scalars, `array`, `object` or class names.
550+
551+
A violation answers `422 invalid_query_arguments`, "argument validation
552+
failed", with per-argument messages in `validation_errors`. The worker
553+
receives the normalized positional list, encoded again. A declared query
554+
without a parameter contract takes a list as given and refuses named
555+
arguments. Native now does the same (`query_contract.rs`). It used to
556+
check positional arguments only, refuse a map, report a violation as a
557+
bare `invalid_query_arguments`, and refuse some contracts as unsupported.
558+
The `query-contract-arguments` fixture records twelve cases against
559+
published 2.11.5
560+
([checkpoint](rust-server-port-checkpoints/2026-10-28-query-contract-arguments.md)).
555561
- **Trace context (Core 2.10, published in Server 2.11.1).** Core adds
556562
nullable `observability_context` columns on runs, commands and tasks.
557563
Native has no carrier: it accepts and ignores a top-level carrier on

‎rust/src/runtime/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ mod previews;
9999
mod prometheus_metrics;
100100
mod queries;
101101
mod query_authority;
102+
mod query_contract;
102103
mod repair_candidates;
103104
mod repair_pass;
104105
mod retention;

0 commit comments

Comments
 (0)