[adr] Behavioral contract for the low-level WebDriver BiDi layer - #17786
[adr] Behavioral contract for the low-level WebDriver BiDi layer#17786titusfortner wants to merge 19 commits into
Conversation
PR Summary by QodoAdd ADR defining behavioral contract for low-level WebDriver BiDi layer
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1.
|
|
Code review by qodo was updated up to the latest commit 98e1858 |
There was a problem hiding this comment.
Pull request overview
Adds a new ADR to the cross-binding decision log that defines a behavioral contract for Selenium’s internal, low-level WebDriver BiDi protocol layer (validation, extensibility, parsing strictness, and surface shape), intended to keep binding implementations aligned on wire-observable behavior.
Changes:
- Introduces a decision record enumerating required outbound/inbound behaviors for the BiDi low-level layer.
- Defines a two-tier taxonomy (Compliance vs Decision) to separate spec-forced requirements from Selenium-standardized choices.
- Records rationale, alternatives, and consequences to support consistent adoption across bindings.
|
Code review by qodo was updated up to the latest commit dfe1c07 |
|
Code review by qodo was updated up to the latest commit 5b77f31 |
|
Code review by qodo was updated up to the latest commit e965829 |
|
Code review by qodo was updated up to the latest commit 1b0e91a |
Code Review by Qodo
1.
|
|
Code review by qodo was updated up to the latest commit 541031b |
|
Code review by qodo was updated up to the latest commit deb870f |
|
Code review by qodo was updated up to the latest commit bdf0687 |
…uire only that malformed input raises
… split, relaxation scope, consistency pass
…n, inbound validation & retention, typed surface
…validity floor, numeric boundary, event params, union resolution)
…ound, and inbound
…le as an undeclared discriminator
…e rule, clarify null-vs-omitted and error-response
…lus vendor-defined extensions
…e via project schema overrides
bdf0687 to
80553c6
Compare
|
Code review by qodo was updated up to the latest commit 80553c6 |
…ral variants (decision 4)
Code Review by Qodo
1. Fidelity exceeds layer boundary
|
| 10. **Preserve received values faithfully.** Fidelity is of the value, not its byte-form: a binding may hold | ||
| any value it parses, a declared field or a retained extra alike, in an ergonomic native type (a 64-bit | ||
| integer for a `js-int`, a date object for a date), provided it loses nothing and can reproduce what the | ||
| wire carried. It must not truncate, round, re-case, or otherwise normalize a value beyond |
There was a problem hiding this comment.
1. Fidelity exceeds layer boundary 🐞 Bug ≡ Correctness
Decision 10 requires reproducing every received number without rounding, but each binding decodes JSON before typed BiDi mapping and several decoders irreversibly narrow decimals to binary floating-point values. Because the ADR excludes transport from scope, the contracted layer cannot recover the original numeric value and therefore cannot satisfy this requirement for valid high-precision JSON numbers.
Agent Prompt
## Issue description
Decision 10 requires lossless reproduction of inbound numeric values, while Decision 4 admits any JSON number. Existing WebSocket/JSON boundaries parse and sometimes round those values before the typed low-level layer receives them, even though transport behavior is declared out of scope.
## Issue Context
Clarify the ownership of lossless JSON decoding. Either include decoding in the behavioral boundary and require lossless numeric parsing before typed mapping, or constrain `number` to a precisely declared representable range/precision and define fidelity against that domain.
## Fix Focus Areas
- docs/decisions/17786-bidi-low-level-behavioral-contract.md[16-18]
- docs/decisions/17786-bidi-low-level-behavioral-contract.md[44-54]
- docs/decisions/17786-bidi-low-level-behavioral-contract.md[84-88]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
…t corruption, not a Java limitation; own the reactive override cost
| - Required-ness is symmetric (decisions 5 and 8): a required field is validated, not represented as absent, | ||
| so it is never null at runtime and no binding widens it to nullable. The cost is backward compatibility — |
There was a problem hiding this comment.
1. Required fields may be null 🐞 Bug ≡ Correctness
The consequence says validating requiredness means a field is never null, but requiredness controls presence while nullability independently permits an explicitly present null. This contradiction can lead binding implementations to reject valid required-and-nullable fields or erase the ADR’s required distinction between omission and explicit null.
Agent Prompt
## Issue description
The consequences conflate a required field being present with it being non-null. Required nullable fields may legitimately contain explicit `null`; only omission must be rejected.
## Issue Context
The ADR already distinguishes omission from explicit `null`, and the projected schema models `required` independently from `nullable`. Reword the consequence so it guarantees that required fields are never represented as omitted or converted to null merely because they were absent.
## Fix Focus Areas
- docs/decisions/17786-bidi-low-level-behavioral-contract.md[145-148]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Code review by qodo was updated up to the latest commit 6f34324 |
📄 The decision, rationale, considered options, and consequences are in the record file this PR adds;
read it there. Below are proposal notes and review logistics.
🔗 Related
since it isn't yet being used (the code currently in use is referred to as "Python now"). Java's adoption
of this contract is in progress in [java][bidi] Add BiDi code generator #17777.
📝 Proposal notes
shared schema makes some behavior cheap to derive, but the contract is still the behavior every binding
exhibits, regardless of how it is implemented.
messages back into typed objects. Transport behavior is out of scope and should be decided separately if
needed.
ADR, showing where each implementation currently stands. The summaries after the table
differentiate the reasons for divergences (marked for replacement, minimizing maintenance costs, or
intentional design choices).
most of the cost versus handwriting: closed-type rejection (6a), null and type checks (7a/7b), typed value
objects (1). The exception is per-type extensibility in static languages: carrying and retaining undeclared
fields on extensible types (6b/9b) stays costly even generated, because preserving and re-serializing them
requires a separate deserialization path (though the webdriverbidi-net project shows it is possible even
when handwritten, despite the cost). Two valid alternatives to the ADR positions are:
implementations).
tolerate what can be represented without inventing data; reject what would produce a false typed
object (a corrupt value or an unknown closed-union variant, 7a/7b/7c). Of the two ways a browser drifts
from the binding's schema, the ADR tolerates one and rejects the other: extra fields a newer browser adds
are tolerated (9a/9b, forward compatibility), while a required field an older browser omits is rejected
(8a) rather than fabricated or tracked as absent, since no static binding can hold an omitted required
field consistently. A project schema override can relax a specific field when a remote end lags, degrading
to a missing field rather than a failed session. The remaining way to tighten drift handling is:
(the layer breaks against a newer browser).
📊 Implementation comparison
How each implementation (current, proposed, and external) stands on selected comparison points against the ADR
The record states ten decisions across three sections (Representation, Outbound, Inbound). The table groups
its comparison points the same way: a row's number is the decision it tests, with letters marking multiple
points under one decision.
Per-implementation through-lines
time, while inbound is whatever falls out of casting the payload onto the object, with no runtime checks,
so 7a/7b/7c and 8a are all ❌ (nothing rejects a malformed or missing value).
closed types reject extras, extensible types carry vendor extras, and both malformed and missing inbound
values error. It is now fully aligned with the inbound decisions; the manual, temporary relaxations it
ships when a browser lags a required field are exactly the project schema overrides this ADR sanctions.
bag on the top-level payload (command params, result, event args), not on the nested types, so a per-type
extra has nowhere to attach, and missing fields get defaults instead of being tracked as absent. Those
choices avoid expensive per-type work, but they are lossy, which is why it diverges on things
that are costly by hand and cheap to generate.
the inbound scalar-arm check in [rb] reject an inbound BiDi scalar outside its union's declared arms #17947).
this contract and diverges on most inbound checks, and is superseded by Python next.
point; [py] update new BiDi layer generation to conform to latest proposed ADR #17942 replaced the tolerated-absence path with rejecting a missing required field.
their declared type (7b — a string or fractional number, or an off-case enum, is coerced instead of
rejected), per-type extras aren't carried or retained (6b/9b), nested types don't warn on an undeclared
field (9c), and scalar-arm unions aren't generated from the schema's
scalarValuessignal (input.Origin).Notes
① Cheaper for static implementations: the type system gives it with little extra code, even
un-generated.
② Cheaper for dynamic implementations: a plain runtime value does the job, with no static wrapper or
typed map.
③ Narrows a small, bounded
js-uintfield (a status code, a line or column number) to a 32-bitint; thegenuinely wide fields (byte sizes, counts) stay
long, so the miss is limited to these small slots.④ Selenium's shared
NumberCoerceris too lenient to enforce this (it truncates fractional integers andaccepts float- or string-encoded numbers); the BiDi layer likely needs a stricter, BiDi-scoped coercer.
⑤ Performs no runtime union dispatch, so an undeclared closed-union variant is not rejected.
⑥ Fills a missing required field with a default (
0/null/false) a caller can't distinguish from a realvalue, so downstream code acts on fabricated data with no signal the field never arrived.
⑦ Compliant, but does not log the warning the ADR requires here.
🗣 Discussion
Notes from Slack and TLC minutes are recorded here as the proposal is discussed.
📌 Tracking
Tracking issue: (linked on acceptance)