Tracking issue for implementing the numeric coercion ADR across flagd, flagd-schemas, flagd-testbed, and all flagd providers.
Contract summary
Per docs/architecture-decisions/numeric-coercion.md (merged in #1979):
- Numeric variants are returned through a numeric accessor only when the conversion is lossless; otherwise
TYPE_MISMATCH.
- Numeric flag values are constrained to the IEEE-754 safe-integer range,
[-(2^53 - 1), 2^53 - 1], in the flag-definition schema. This is an ordinary schema validation, surfaced like any other schema validation (today flagd logs schema violations as warnings; that behavior is unchanged). Hard-failing a load on schema violations is intentionally out of scope for this work and is tracked separately as its own feature.
- The lossless-coercion contract applies identically across gRPC, OFREP, and in-process evaluation.
- Int bounds are relative to the accessor in use. Accessor widths differ by language; the shared testbed treats
Long (int64) as the canonical wide numeric accessor and only encodes language-agnostic assertions, with int32-specific cases behind @int32-bounded.
Per-language accessor widths
| Language |
Integer accessor |
Long accessor |
Notes |
| Go (flagd-core) |
int64 (ResolveIntValue) |
same (int64 is canonical) |
no separate Long; Integer is the canonical Long, so Go excludes @int32-bounded |
| Java |
int32 (getInteger) |
int64 (getLong, java-sdk#1985) |
Long safe-delegates to int; canonical wide accessor |
| .NET |
int32 |
int64 (Int64) |
|
| Python |
arbitrary-precision int |
n/a |
effectively wide |
| JS / Web |
double |
double |
no int64; exact up to 2^53 - 1 (the cap) |
Float accessors cap at the safe-integer range.
No wire format changes. AnyFlag.double_value in ResolveAll remains as-is; the ADR intentionally treats the parsed int/float JSON type as non-authoritative, which keeps bulk eval faithful under the same rule.
Breaking change scope
- Callers relying on silent truncation (Go, Java in-process):
3.14 via getIntegerDetails returns TYPE_MISMATCH instead of 3.
- Some SDKs flip the other way:
10.0 via getIntegerDetails returns 10 (previously TYPE_MISMATCH in Python and .NET).
- Config authors: flag definitions with numeric values outside
[-(2^53 - 1), 2^53 - 1] fail schema validation (a load-time warning today, consistent with every other schema validation). They are not hard-rejected at load by this work.
flagd is pre-1.0, so the coercion behavior change ships as a feat!: minor bump with breaking changes called out in release notes.
Testbed rollout
There is no existing numeric-coercion suite to preserve. This work adds a new, opt-in @numeric-coercion tagged suite (following the opt-in @fractional-v1/@fractional-v2 precedent) so providers adopt it as they migrate. Coverage is added to both the provider gherkin/ suite and the self-contained evaluator/ suite. Width-specific expectations sit behind @int32-bounded; wide and boundary values use the canonical Long accessor.
Rollout order
flagd-schemas: add the safe-integer range to numeric variant values (schema validation)
flagd-testbed: add ADR-conformant scenarios under @numeric-coercion (both suites), release new tag
flagd + flagd-core (Go): implement the lossless-coercion contract, bump testbed submodule
- Providers (in parallel once the testbed tag lands): bump testbed submodule, implement the lossless-coercion contract in each in-process resolver, apply the same lossless check client-side for
AnyFlag.double_value
- Coordinated provider releases
Child issues
Related
Tracking issue for implementing the numeric coercion ADR across flagd, flagd-schemas, flagd-testbed, and all flagd providers.
Contract summary
Per
docs/architecture-decisions/numeric-coercion.md(merged in #1979):TYPE_MISMATCH.[-(2^53 - 1), 2^53 - 1], in the flag-definition schema. This is an ordinary schema validation, surfaced like any other schema validation (today flagd logs schema violations as warnings; that behavior is unchanged). Hard-failing a load on schema violations is intentionally out of scope for this work and is tracked separately as its own feature.Long(int64) as the canonical wide numeric accessor and only encodes language-agnostic assertions, with int32-specific cases behind@int32-bounded.Per-language accessor widths
ResolveIntValue)@int32-boundedgetInteger)getLong, java-sdk#1985)Int64)2^53 - 1(the cap)Floataccessors cap at the safe-integer range.No wire format changes.
AnyFlag.double_valueinResolveAllremains as-is; the ADR intentionally treats the parsed int/float JSON type as non-authoritative, which keeps bulk eval faithful under the same rule.Breaking change scope
3.14viagetIntegerDetailsreturnsTYPE_MISMATCHinstead of3.10.0viagetIntegerDetailsreturns10(previouslyTYPE_MISMATCHin Python and .NET).[-(2^53 - 1), 2^53 - 1]fail schema validation (a load-time warning today, consistent with every other schema validation). They are not hard-rejected at load by this work.flagd is pre-1.0, so the coercion behavior change ships as a
feat!:minor bump with breaking changes called out in release notes.Testbed rollout
There is no existing numeric-coercion suite to preserve. This work adds a new, opt-in
@numeric-coerciontagged suite (following the opt-in@fractional-v1/@fractional-v2precedent) so providers adopt it as they migrate. Coverage is added to both the providergherkin/suite and the self-containedevaluator/suite. Width-specific expectations sit behind@int32-bounded; wide and boundary values use the canonicalLongaccessor.Rollout order
flagd-schemas: add the safe-integer range to numeric variant values (schema validation)flagd-testbed: add ADR-conformant scenarios under@numeric-coercion(both suites), release new tagflagd+ flagd-core (Go): implement the lossless-coercion contract, bump testbed submoduleAnyFlag.double_valueChild issues
Related