Skip to content

Wire-contract parity: per-call host resolution, sync checkout_decide, and dropoff_latitude - #101

Draft
tomas-amaro wants to merge 3 commits into
masterfrom
parity/transport-tranche
Draft

Wire-contract parity: per-call host resolution, sync checkout_decide, and dropoff_latitude#101
tomas-amaro wants to merge 3 commits into
masterfrom
parity/transport-tranche

Conversation

@tomas-amaro

@tomas-amaro tomas-amaro commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What this is

Wire-contract parity fixes for the PHP SDK, derived from the language-neutral
Riskified contract corpus and audited against the C# reference implementation
(Riskified/sdk_net @ 9165cf5). Two commits, production source plus regression
tests.

Changes

Sticky host selection (data-leak class)

AbstractTransport::$url was reassigned in place by decideOrder, eligible, opt_in and all eight account methods, and never restored. Once an instance called login(), every later call on it — including submitOrder() — went to api.riskified.com, silently cross-posting order data to the Account Secure host.

The host family is now scoped to the single call that asked for it (on_host()), and endpoint_prefix() resolves the host through host() per request. $url is written only by the constructor. The public API is unchanged: no constructor, method name, or signature moved.

checkout_decide() posted to the wrong production host

It posts to /api/decide, and the C# reference has exactly one /api/decide call site, unconditionally FlowStrategy.Sync (Orders/OrdersGateway.cs:142). In production it was posting to wh.riskified.com; sandbox is unaffected, having no sync host. Cross-checking every PHP endpoint against the full BuildUrl inventory turned up no other unwrapped call to a path an on_host() family serves.

dropoff_latitude — reverted, this SDK was already correct

An earlier revision of this branch renamed LineItem's dropoff_latitude to the transposed dropoff_latitiude, and added a $_field_aliases mechanism to AbstractModel so the correct spelling kept working as a deprecated input name. Both have been reverted.

The rename rested on a contract-corpus claim that the transposition was the live wire name, on the grounds that the reference C# SDK sends it. That inference was wrong:

  • Both OpenAPI specs declare dropoff_latitude, with a description and an example. The transposed spelling appears in neither, nor anywhere else in the reference export.
  • It occurs exactly once in all of sdk_net (OrderElements/RideTicketLineItem.cs:100), with no test covering it, while pickup_latitude (:89) and dropoff_longitude (:104) in the same class are spelled correctly.

So the transposition is a defect in the C# SDK, not the contract. This SDK had the name right; the rename would have broken a field that worked, and the alias existed only to soften that break. AbstractModel is restored to master — the resolve_field() indirection and the E_USER_DEPRECATED path had exactly one consumer, so keeping them would leave dead code rather than a general facility.

The corpus has been corrected in Riskified/sdk-orchestrator.

Tests

  • tests/OrderWebhook/Transport/HostSelectionTest.php, with a RecordingTransport double that records the URL each call would have used.
  • tests/OrderWebhook/Model/LineItemTest.php — asserts dropoff_latitude is the emitted key and dropoff_latitiude is not, that the transposed name is rejected as an unknown property, and that all four ride geolocation fields use their contract names. Guarded in both directions, since this branch pinned it the wrong way once.

Both are reported to fail at c0b0f87.

Deliberately unchanged

Null handling and the always-emitted defaults, the vendor Accept header, api-version, hash_equals, old_status, error parsing, retries and timeouts.

An order serialized before and after this branch is byte-identical: no payload key changes in either direction. (An earlier revision of this branch changed one — dropoff_latitudedropoff_latitiude — and that has been reverted.)

Reviewer notes

  • Branch is based on c0b0f87 and is one commit behind master (3b6652e). It merges cleanly.
  • The test suite was not executed in the environment where these commits were authored; please let CI be the judge.

🤖 Generated with Claude Code

tomas-amaro and others added 3 commits August 26, 2026 10:43
Two wire-contract parity defects from docs/parity/FLEET.md sections 3 and 6.

1. Sticky host selection. AbstractTransport::$url was reassigned in place by
   decideOrder, eligible, opt_in and all eight account methods, and never
   restored. Once an instance called login(), every later call on it - including
   submitOrder() - went to api.riskified.com, silently cross-posting order data
   to the Account Secure host. The host family is now scoped to the single call
   that asked for it (on_host()), and endpoint_prefix() resolves the host through
   host() per request. $url is now written only by the constructor. The public
   API is unchanged: no constructor, method name, or signature moved.

2. dropoff_latitiude. LineItem declared the corrected spelling
   dropoff_latitude, which the API does not read, so ride-hailing dropoff
   geolocation was dropped without an error. The live wire name is the transposed
   dropoff_latitiude - see docs/flows/01-model-catalog.md section 5 and sdk_net
   @ 9165cf5 OrderElements/RideTicketLineItem.cs:101. Restored, with a comment
   and a test asserting the misspelling so it is not "corrected" again.

Regression tests: tests/OrderWebhook/Transport/HostSelectionTest.php (with a
RecordingTransport double that records the URL each call would have used) and
tests/OrderWebhook/Model/LineItemTest.php. Both fail at c0b0f87.

Null handling, the vendor Accept header, api-version, hash_equals, old_status,
error parsing, retries and timeouts are deliberately untouched.
…to sync

Two follow-ups to the previous commit.

1. A general field-alias mechanism, so restoring the dropoff_latitiude wire name
   is no longer a caller-visible break. AbstractModel gains $_field_aliases,
   an alias => canonical field map consulted by the constructor, __set and __get
   through resolve_field(). A value written through an alias is stored, validated
   and serialized under the canonical field name, so an alias can never add,
   duplicate or rename a key on the wire. Writing through one raises
   E_USER_DEPRECATED naming the field to use; reads are silent, to avoid
   repeating the same signal. Unknown properties still throw
   InvalidPropertyException. LineItem declares the one alias we need today,
   dropoff_latitude => dropoff_latitiude; no other field is retrofitted.

2. checkout_decide() now resolves the sync host, like decideOrder(). It posts to
   /api/decide, and the C# reference has exactly one /api/decide call site,
   unconditionally FlowStrategy.Sync (sdk_net @ 9165cf5,
   Orders/OrdersGateway.cs:142 - the full BuildUrl inventory confirms there is no
   checkout-specific decide on FlowStrategy.Default). In production it was
   posting to wh.riskified.com; sandbox is unaffected, having no sync host.
   Cross-checking every PHP endpoint against that inventory turned up no other
   unwrapped call to a path an on_host() family serves.

The wire payload is unchanged: an order serialized before and after this commit
is byte-identical, and against c0b0f87 the only difference in the whole tranche
is the single dropoff_latitiude key. Null handling and the always-emitted
defaults remain untouched.
…me and the alias

Reverts the two dropoff changes made earlier on this branch: the rename of
`dropoff_latitude` to `dropoff_latitiude`, and the `$_field_aliases` machinery
added to keep the correct spelling working as a deprecated input name.

Both rested on a contract-corpus claim that the transposed `dropoff_latitiude`
was the live wire name, because the reference C# SDK sends it. That inference
was wrong:

  * Both OpenAPI specs declare `dropoff_latitude`, with a description and an
    example. The transposed spelling appears in neither.
  * It occurs exactly once in all of sdk_net
    (Riskified.SDK/Model/OrderElements/RideTicketLineItem.cs:100), with no test
    covering it, while pickup_latitude (:89) and dropoff_longitude (:104) in the
    same class are spelled correctly.

This SDK already had the name right. The rename would have broken a field that
worked, and the alias existed only to soften that break.

  * LineItem: field restored to `dropoff_latitude`; the alias map removed.
  * AbstractModel: restored to master. The whole alias mechanism -- the
    resolve_field indirection through __get/__set/__construct and the
    E_USER_DEPRECATED path -- had exactly one consumer, so it is dead code now
    rather than a general facility.
  * LineItemTest: the alias and deprecation tests are gone. What replaces them
    asserts `dropoff_latitude` is the emitted key and `dropoff_latitiude` is
    not, that the transposed name is rejected as an unknown property, and that
    all four ride geolocation fields use their contract names. The
    unknown-property coverage of AbstractModel is kept -- master had none.

The corpus has been corrected in Riskified/sdk-orchestrator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tomas-amaro tomas-amaro changed the title Wire-contract parity: per-call host resolution, sync checkout_decide, and dropoff_latitiude Wire-contract parity: per-call host resolution, sync checkout_decide, and dropoff_latitude Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant