Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import dev.openfeature.contrib.tools.providertck.AbstractProviderTckTest;
import dev.openfeature.contrib.tools.providertck.BackendEndpoint;
import dev.openfeature.contrib.tools.providertck.Capability;
import dev.openfeature.contrib.tools.providertck.KnownDeviation;
import dev.openfeature.sdk.FeatureProvider;
import java.io.File;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -89,9 +89,11 @@ public FeatureProvider createUnavailableProvider() {
/**
* {@inheritDoc}
*
* <p>Everything except {@link Capability#STRICT_NUMERIC_TYPING}. Evaluating {@code float-flag}
* (0.5) through the integer API returns {@code 0} with <em>no</em> error code rather than
* {@code TYPE_MISMATCH} with the code default — the value is silently truncated. That is a
* <p>Everything declarable except {@link Capability#NUMERIC_COERCION}. Evaluating
* {@code float-flag} (0.5) through the integer API returns {@code 0} with <em>no</em> error code
* rather than {@code TYPE_MISMATCH} with the code default — the value is silently truncated.
* Coercion as such is permitted, and the capability says so: the rule is that a lossless
* coercion must succeed and a lossy one must fail. It is the lossy case being accepted that is a
* defect to fix, not a design choice; this override should be deleted once it is.
*
* <p>Declared here rather than per mode because both resolvers behave identically, which places
Expand All @@ -101,10 +103,45 @@ public FeatureProvider createUnavailableProvider() {
* <p>That includes {@link Capability#LIFECYCLE}, and legitimately so: flagd reaches its backend
* during initialisation in both modes — an RPC round trip, or a full ruleset sync — so the
* lifecycle scenarios assert something real here rather than passing vacuously.
*
* <p>{@link Capability#declarableExcept} rather than {@code EnumSet.complementOf}, which is what
* this used to be. The complement of one capability is every other <em>enum constant</em>,
* including {@code @targeting} and {@code @caching} — reserved tags no scenario carries — so a
* report emitted from here claimed two capabilities nothing had examined.
*/
@Override
public Set<Capability> capabilities() {
return EnumSet.complementOf(EnumSet.of(Capability.STRICT_NUMERIC_TYPING));
return Capability.declarableExcept(Capability.NUMERIC_COERCION);
}

/**
* {@inheritDoc}
*
* <p>The withheld {@link Capability#NUMERIC_COERCION} is a defect, not a limitation, and
* the report has to say so. In the results stream the two are indistinguishable: the scenario is
* skipped either way, and the declaration explains only <em>that</em> the capability was not
* claimed, never whether flagd chose not to claim it. A consumer comparing providers would
* otherwise read this exactly as it reads a provider with no streaming transport declining
* {@code @configuration-change}, which is a decision rather than a bug.
*
* <p>Tracked against flagd's numeric coercion ADR, which is where the rule this deviates from is
* settled: coercion is permitted when it is lossless and must fail with {@code TYPE_MISMATCH}
* only when information would be lost. The summary says which half is broken, because "flagd
* coerces numbers" on its own reads as a description of intended behaviour. Delete the entry —
* and the {@code capabilities()} override above — once the lossy case reports
* {@code TYPE_MISMATCH}.
*/
@Override
public List<KnownDeviation> knownDeviations() {
return Collections.singletonList(KnownDeviation.tracked(
Capability.NUMERIC_COERCION,
"https://github.com/open-feature/flagd/issues/1996",
"The lossy half of the coercion rule is not enforced: evaluating float-flag (0.5) "
+ "through the integer API returns 0 with no error code, rather than "
+ "TYPE_MISMATCH with the code default, so the fractional part is discarded "
+ "silently. Lossless coercion is permitted and is not the defect. Both "
+ "resolvers behave identically, which places it in the shared provider layer "
+ "rather than in either transport."));
}

private FlagdOptions.FlagdOptionsBuilder baseOptions() {
Expand Down
Loading
Loading