Skip to content

The provider contract does not say what to do when a numeric value does not fit the requested accessor #430

Description

@aepfli

The gap

The specification defines one numeric type, deliberately:

A numeric value of unspecified type or size. Implementation languages may further differentiate between integers, floating point numbers, and other specific numeric types and provide functionality as idioms dictate.
types.md

Accordingly, Conditional Requirement 1.3.2.1 and Requirement 2.2.2.1 both say "boolean, numeric, string, and structure" — one numeric type, not two.

But every typed-language SDK takes up the idiom the spec permits and exposes two accessors — getIntegerDetails and getFloatDetails, ResolveIntValue and ResolveFloatValue. And at that point a question arises that no requirement answers:

What must a provider do when a flag's value does not fit the accessor it was asked through?

float-flag resolves to 0.5. An application calls the integer accessor. Is that "a requested type that cannot be satisfied" under Requirement 2.2.7, obliging TYPE_MISMATCH and the code default? Or is narrowing acceptable? The specification does not say, and 0.5 is not a corner case — it is the ordinary consequence of a two-accessor SDK over a one-type wire format.

Why this is worth closing rather than leaving to idiom

Two independent projects have had to invent a rule to fill the vacuum, and they did not invent the same one.

flagd wrote an ADR (numeric coercion, open-feature/flagd#1995, open-feature/flagd#1996), explicitly scoped "across flagd implementations". Its rule: coercion is permitted when lossless10.0 -> 10 succeeds — and must fail with TYPE_MISMATCH only when information would be lost. It additionally distinguishes accessor width, since a 64-bit integer accessor and a 32-bit one are not interchangeable, tagged @int32-bounded in its testbed.

The provider conformance suite (#417, #423) had to model it as an optional capability — originally @strict-numeric-typing, since renamed @numeric-coercion to stop the two vocabularies forking. Mapping the suite onto the numbered requirements showed it to be the only capability tracing to no requirement at all, which at the time looked like a wart in the suite. It is not: it is a faithful reflection of the specification declining to specify this.

Meanwhile the observed behaviour is poor in a way users would care about. flagd narrows 0.5 to 0 with no error code at all, in Go and in Java, in both resolvers:

flag "float-flag" resolved to 0 (int64), expected 1 (int64)

The application receives a plausible value and no signal that anything happened. That is the worst failure mode for a feature flag, and it went unnoticed until a conformance suite asked the question.

The question

Should the provider section say what happens here, or should the specification state positively that it does not?

Three shapes, with the trade-off each carries:

  1. Lossless coercion permitted, lossy coercion must return TYPE_MISMATCH. Matches flagd's ADR, so the reference implementation and the specification would agree, and 10.0 -> 10 keeps working for the many flag sets that store whole numbers as JSON numbers. Requires the specification to acquire a notion of losslessness, and probably of accessor width with it.
  2. Any cross-type request must return TYPE_MISMATCH. Simplest to state and to test. Breaks 10.0 -> 10, which is likely to break real deployments, since nothing has ever told flag authors that 10 and 10.0 differ.
  3. Say explicitly that it is language-idiomatic and unspecified. Honest and cheap, and legitimises what implementations already do. But it means two conforming providers can answer the same evaluation differently, which is precisely what an application author reads a specification to rule out.

I lean towards (1), because it is already implemented by the reference provider and because (3) makes "conforming" weaker than users will assume. But (3) at least stops the silence being mistaken for an omission, and is much better than the present state, where the answer exists only inside one implementation's ADR and one appendix's capability tag.

Either way, something should be recorded — the current position is that the most widely deployed provider silently returns wrong values for a legal call, and no requirement is being violated when it does.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions