docs: add numeric coercion proposal#1979
Conversation
* handles numeric coercion * defines limits for numerics Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
✅ Deploy Preview for polite-licorice-3db33c ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Code Review
This pull request introduces a new Architecture Decision Record (ADR) establishing a consistent numeric coercion contract for typed flag accessors across all flagd implementations. It proposes allowing coercion only when lossless and capping numeric values within the IEEE-754 safe-integer range to prevent silent truncation, overflow, or precision loss. Feedback on the ADR suggests improving mathematical precision in the proposal table by explicitly referencing the "safe-integer range" rather than just "2^53-1" to clearly account for negative bounds.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| | int, exceeds the maximum of the numeric resolver in use, but within 2^53-1 | `TYPE_MISMATCH` | value (widened) | | ||
| | int, exceeds 2^53-1 | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | |
There was a problem hiding this comment.
For mathematical precision, it is better to refer to the safe-integer range [-(2^53 - 1), 2^53 - 1] rather than just 2^53-1 or within 2^53-1, as negative integers can also exceed the safe range in absolute value (e.g., -9007199254740992). Using "outside the safe-integer range" or "within the safe-integer range" makes this explicit.
| | int, exceeds the maximum of the numeric resolver in use, but within 2^53-1 | `TYPE_MISMATCH` | value (widened) | | |
| | int, exceeds 2^53-1 | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | | |
| | int, exceeds the maximum of the numeric resolver in use, but within the safe-integer range | <code>TYPE_MISMATCH</code> | value (widened) | | |
| | int, outside the safe-integer range <code>[-(2^53 - 1), 2^53 - 1]</code> | rejected at load (or <code>PARSE_ERROR</code> at evaluation if not validated) | rejected at load (or <code>PARSE_ERROR</code> at evaluation if not validated) | |
| flagd additionally caps numeric flag values at the IEEE-754 safe-integer range, `[-(2^53 - 1), 2^53 - 1]`. | ||
| Variants whose absolute value exceeds this range are considered invalid per the JSON schema (we'll add this limit there). |
There was a problem hiding this comment.
This is somewhat opinionated, but for now I think I'd rather define a hard cap than say we'll possibly lose precision at some integer value. This number is sufficiently high, IMO, it can support extremely large values such as 8 petabytes (in bytes), or timestamps in millis.
|
|
||
| ## Considered options | ||
|
|
||
| 1. **Lossless coercion only, with a hard cap at the JSON-safe integer range (2^53 - 1)**: an accessor returns a value if and only if the conversion is lossless. |
There was a problem hiding this comment.
Is it the JSON max safe integer or the JavaScript max safe integer? From what I read into the spec, there is no max safe int (https://www.json.org/json-en.html) in JSON. I think we should use the wording IEEE 754 64-Bit max safe integer instead, because this limitation does not only apply to JavaScript, as it is done elsewhere in this document
There was a problem hiding this comment.
You're right, this is mostly a JS not a JSON limitation. The reason it involves JSON is because many JSON parsers (including JS's) don't support parsing integers larger than the JS number in question, even some Java parsers by default have this issue (Gson I believe) so it gets a bit messy.
But I fully agree that the IEEE 754 64-Bit max safe integer is a better way to talk about this number so I will use that naming (I have in other places in the doc already).
| | int, exceeds the maximum of the numeric resolver in use, but within 2^53-1 | `TYPE_MISMATCH` | value (widened) | | ||
| | int, exceeds 2^53-1 | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | | ||
| | float, whole-valued and within the resolver's int range (e.g. `10.0`) | value (e.g. `10`) | value | | ||
| | float, fractional or out of the resolver's int range | `TYPE_MISMATCH` | value | |
There was a problem hiding this comment.
I think this is may be an inconsistency: IIRC, we do not distinguish numbers in the flagd config per type (int vs float), we just have a Number type.
With this rule, however, fetching a flag that evaluates to 2^53 + 1 (int) would fail no matter if it is fetched as int or float, but 2^53 + 1.1 (float) would work when fetched as float. I think that all values that fit inside an IEEE 754 64-Bit max safe integer should be safely returned when fetching a float flag.
There was a problem hiding this comment.
I think this is may be an inconsistency: IIRC, we do not distinguish numbers in the flagd config per type (int vs float), we just have a Number type.
We don't in the spec, but languages and flagd does (in terms of how the resolvers behave).
With this rule, however, fetching a flag that evaluates to 2^53 + 1 (int) would fail no matter if it is fetched as int or float, but 2^53 + 1.1 (float) would work when fetched as float. I think that all values that fit inside an IEEE 754 64-Bit max safe integer should be safely returned when fetching a float flag.
I think my table was not clear; it's mostly about what happens if you evaluate a fractional decimal number with some kind of int resolver. For float evaluations themselves - I think since all languages support IEEE-754 we don't need to say much. 2^53 + 1.1 from your example is specifically classed as out-of-bounds on the config level by this proposal, though the table didn't explicitly mention that (it was just mentioned in prose elsewhere). I've included that now, but the fetched as float column is pretty unsurprising and uninteresting.
| flagd additionally caps numeric flag values at the IEEE-754 safe-integer range, `[-(2^53 - 1), 2^53 - 1]`. | ||
| Variants whose absolute value exceeds this range are considered invalid per the JSON schema (we'll add this limit there). | ||
|
|
||
| | Variant kind | Fetched as Integer | Fetched as Float | |
There was a problem hiding this comment.
How do we treat values that exceed even the maximum finite number that can be represented with IEEE 754 64-Bit? I think we should call out explicitely that we will reject those, since they can be represented in JSON
There was a problem hiding this comment.
Maybe I'm confused, but this proposal caps numeric flags at the iEEE 754 max safe int:
flagd additionally caps numeric flag values at the IEEE-754 safe-integer range,
[-(2^53 - 1), 2^53 - 1].
This would make it invalid to configure such a value; what happens if the config validation is ignored can be decided here, but I would suggest PARSE_ERROR.
| | ------------------------------------------------------------- | ------------------ | ----------------- | | ||
| | int, fits the maximum of the numeric resolver in use | value | value (widened) | | ||
| | int, exceeds the maximum of the numeric resolver in use, but within 2^53-1 | `TYPE_MISMATCH` | value (widened) | | ||
| | int, exceeds 2^53-1 | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | rejected at load (or `PARSE_ERROR` at evaluation if not validated) | |
There was a problem hiding this comment.
I don't really like the idea that we reject values that we could still safely represent. E.g. in Java, we do have the option to represent 64 bit integers, but we would still reject the numbers between 2^53 and 2^64 for no apparent reason for a Java developer.
Should we in this case refer to the used programming language? I.e. if no such value exists due to limitations of the language, e.g. JavaScript, we return PARSE_ERROR, else we return the value. Programmers would already know which numbers their languages can handle and which it cannot.
There was a problem hiding this comment.
Programmers would already know which numbers their languages can handle and which it cannot.
Programmers do, but I think there's a desire for cross languages consistency - and for that reason I feel that putting an upper bound on what flagd supports is a good idea. For OpenFeature itself, I agree with you: defer to the language - but I would like to assure users that flagd operates consistent for numeric values across all languages, event if it involves a hard cap, personally.
Imagine this real use case:
As a PM, I want to define the amount in of data in bytes that a customer can store for some purpose
I don't want the PM to have to consider the numeric types of Python/Java/JS - I just want the configuration plane to give them solid feedback. If we aren't consistent here, it's likely that a some validation that passes in the UI doesn't work in the backend, or some batch job, because of precision loss. If we prevent the configuration of such values in the first place, it seems like a good trade-off to me. (edited)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
|



Fixes: #1978