Skip to content

feat: add the provider conformance assets - #423

Draft
aepfli wants to merge 2 commits into
mainfrom
feat/provider-tck-appendix
Draft

aepfli wants to merge 2 commits into
mainfrom
feat/provider-tck-appendix

Conversation

@aepfli

@aepfli aepfli commented Aug 24, 2026 •

Copy link
Copy Markdown
Member

Part of #417. This PR is the assets — the files four conformance implementations execute. The normative description of them, Appendix F, is #439, stacked on this. #425 adds the machine-readable report.

This PR previously carried the appendix too. It was split so the assets can land while the prose is still being reviewed.

What

The artifacts a provider conformance suite runs against:

path what it is
assets/provider-tck/gherkin/ six feature files — evaluation, errors, events, lifecycle, reason, metadata
assets/provider-tck/flags/canonical-flags.json the flag set every scenario assumes
assets/provider-tck/openapi/control-api.yaml the HTTP surface a backend under test must expose
assets/provider-tck/go.mod, embed.go a nested Go module exposing the artifacts as an embed.FS
release-please-config.json and the workflow releases, for the specification and for the assets separately

Marked experimental: the scenario set is a representative subset, and the capability vocabulary may still change.

Why this is split out

Four language implementations already run these files, and all four are currently pinned to an unmerged branch tip. Every rebase here invalidates them — it has happened three times in two weeks. Nothing about those pins becomes real until the assets are on main with a tag, so landing the executable half first is worth more than landing it together with prose that will take longer to review.

Appendix F is the same work, reviewed on its own terms.

Why a Go module is in here

A Go module is distributed as a zip of the VCS tree, in which a git submodule is only a gitlink and its files are absent. The other three languages build from a working tree and keep using the submodule, but a Go suite would have to vendor a copy of every artifact and police it against drift. embed.go removes the copy: a consumer pins a revision, the checksum database makes it immutable, and the embedded bytes are the same bytes every other language reads out of the submodule. go.mod and embed.go are inert for every non-Go consumer. Precedent: open-feature/flagd-schemas.

The release config tags the assets as their own component, specification/assets/provider-tck/vX.Y.Z — the shape go get needs. Without it a Go consumer can only name a pseudo-version.

Release configuration, and #431

Two packages. The specification keeps bare vX.Y.Z tags and excludes the assets path, so an asset-only change does not cut a spec release. The assets are a Go component with tag-separator: "/".

This replaced #431, which configured the specification package alone and is now closed. Everything in it is here, including both fixes from its review (issues: write, and target-branch rather than the command/default-branch inputs v5 ignores).

The capability vocabulary lives in the README, for now

A scenario exercising an optional part of the contract carries a tag; a provider declares what it supports, and a scenario gated on an undeclared tag is reported skipped, with the reason — never as passed. A suite that quietly goes green on scenarios it did not run is worse than no suite at all.

That rule and the tag table are in assets/provider-tck/README.md so the tags in the feature files are not left undefined while the appendix is in review. #439 moves them into the appendix and leaves a pointer behind, so they end up with one home rather than two that can disagree.

Worth stating plainly: withholding a tag is not an admission of a defect. @numeric-coercion borrows its rule from flagd's coercion ADR, and @string-typing / @fully-typed-values sit on a question the specification does not answer at all — #430 and #433.

Decisions worth reviewing

  • assets/provider-tck/ rather than beside the SDK Gherkin, because assets/gherkin/evaluation.feature already exists and tests an SDK, not a provider. This answers [Tracking] A cross-language conformance suite for OpenFeature providers #417's Q1 — the subdirectory is forced rather than chosen.
  • .gitattributes normalises these to LF. Four languages consume them byte for byte and copy them verbatim into published artifacts, so a Windows checkout must not package differently from a Linux one.
  • missing-flag must not exist in the flag set — its absence is what the FLAG_NOT_FOUND scenario tests. The other four load-bearing properties of the flag set are in the README; each one, if seeded wrong, turns a scenario green or red for the wrong reason in every language at once.
  • No container restarts. Outages are produced through the control API, because orchestrators reassign host ports on restart and every provider pointed at the old one then fails looking flaky.

Implementations

All four execute exactly these files, against flagd (both resolvers), OFREP and Flagsmith:

suite flagd OFREP Flagsmith
Java #1830 #1847 #1840 #1849
Go #940 #941 #942 #959
JavaScript #1606 #1608 #1612 #1623
Python #409 #411 #414 —

What running them has found

  • flagd silently narrows a float flag to an integer, Go and Java, both resolvers, no error code — flagd#1996
  • flagd reported ready before flags were evaluable, on 100% of starts — flagd#2047, fixed in flagd v0.17.0
  • The Java SDK's MultiProvider swallows child provider events — java-sdk#1882
  • The Python flagd provider widens a boolean to a float — python-sdk-contrib#417
  • The JavaScript Flagsmith provider stringifies unconditionally where its other paths correctly report a mismatch — visible only because three languages ran identical scenarios against one backend
  • two specification gaps, raised rather than decided here: #430, #433

@coderabbitai

coderabbitai Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Aug 24, 2026
…pec submodule

The Gherkin, the canonical flag set and the control API document are not Java
artifacts. They are language-agnostic definitions of the provider contract that
every language's TCK must agree on byte for byte, and they only lived in this
module because the proof of concept had to start somewhere.

They now live in open-feature/spec as Appendix F, under
specification/assets/provider-tck/, and are copied in from the `spec` git
submodule at generate-resources — the same mechanism tools/flagd-api-testkit
already uses for the flagd test harness. The copies are git-ignored and carry a
do-not-edit note; changes belong in the spec repo and arrive here by bumping the
submodule.

Consumers are unaffected: the artifacts are still packaged into the release JAR,
@SelectClasspathResource("features") still resolves, and nobody needs a submodule
of their own. Verified byte-identical after the round trip.

The in-memory CI job now checks out submodules, since without them there is no
suite to run.

DEPENDS ON open-feature/spec#423. The submodule is pinned to that PR's branch
commit rather than to a commit on the spec repo's main branch. That is reachable,
so CI can fetch it, but it must be re-pinned to main once #423 merges and before
this lands.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Aug 24, 2026
…pec submodule

The Gherkin, the canonical flag set and the control API document are not Java
artifacts. They are language-agnostic definitions of the provider contract that
every language's TCK must agree on byte for byte, and they only lived in this
module because the proof of concept had to start somewhere.

They now live in open-feature/spec as Appendix F, under
specification/assets/provider-tck/, and are copied in from the `spec` git
submodule at generate-resources — the same mechanism tools/flagd-api-testkit
already uses for the flagd test harness. The copies are git-ignored and carry a
do-not-edit note; changes belong in the spec repo and arrive here by bumping the
submodule.

Consumers are unaffected: the artifacts are still packaged into the release JAR,
@SelectClasspathResource("features") still resolves, and nobody needs a submodule
of their own. Verified byte-identical after the round trip.

The in-memory CI job now checks out submodules, since without them there is no
suite to run.

DEPENDS ON open-feature/spec#423. The submodule is pinned to that PR's branch
commit rather than to a commit on the spec repo's main branch. That is reachable,
so CI can fetch it, but it must be re-pinned to main once #423 merges and before
this lands.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Aug 24, 2026
lifecycle.feature was tagged @events, which is wrong in both directions.

Too strict: a stateless provider such as OFREP emits no events of its own
and cannot declare @events, yet it still initialises against a backend and
still owes the lifecycle contract.

Too lax: FeatureProviderStateManager emits PROVIDER_READY/PROVIDER_ERROR
around initialize for any provider, whether or not it is an EventProvider.
So a provider that does no initialisation of its own reaches READY exactly
as NoOpProvider would, and the readiness scenario passes vacuously.

Adds Capability.LIFECYCLE ("@lifecycle") -- performs an initialisation that
reaches its backend, with an observable outcome -- and re-vendors
lifecycle.feature verbatim from the spec assets, where the feature-level tag
is now @lifecycle (spec dfa16586, PR open-feature/spec#423).

flagd declares LIFECYCLE in both resolver modes: RPC does a round trip and
in-process syncs the whole ruleset during initialisation, so the scenarios
assert something real there.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/js-sdk-contrib that referenced this pull request Aug 24, 2026
…odule

The feature files, canonical flag set and control-API document are owned by
open-feature/spec. Vendoring them here made this repository a second place the
definition of conformance could drift, which is precisely what the suite exists
to prevent. They are now a pinned submodule at libs/shared/provider-tck/spec
and the copies are gone.

Adopters are unaffected, and that is the constraint the change had to respect:
the rollup asset globs copy the artifacts out of the submodule and into the
published package, so installing @openfeature/provider-tck from npm still needs
no submodule and no particular repository layout. resolveAssetDir therefore has
to satisfy two layouts -- the packaged copy next to the bundle, and the
submodule under the library root -- and tries both. The spec calls the feature
directory `gherkin`; the package keeps the name the API talks about.

Contributors do need the submodule: without it no feature file loads at all.
`nx test` and `nx package` depend on a pullSpec target that initialises it, and
CI already checks out with `submodules: recursive`. Prettier is pointed at the
submodule instead of the old vendored paths so it never rewrites artifacts that
are consumed byte for byte by every language's TCK.

The .gitattributes normalising those files to LF goes with them; the equivalent
lives upstream, where the files now do.

Pinned to dfa1658 (open-feature/spec#423).

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
@aepfli
aepfli force-pushed the feat/provider-tck-appendix branch from 4661068 to 36392a9 Compare August 24, 2026 14:08
@@ -0,0 +1,82 @@
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the canonical flags should also be gherkin? They could be a very large "given", I guess.

@aepfli aepfli Sep 10, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can see the motivition behind it. but i am not sure it will be easier to read or understand or parse. Especially a json is easily transformed into another JSON for eg. CLI's to prefill databases etc. Not so sure about the gherkin for this purpose.

Comment on lines +30 to +45
Scenario: An integer flag resolves as an integer
# Paired with the float scenario below and with the narrowing scenario in errors.feature.
# Together they pin down that the two numeric types stay distinct rather than both being
# funnelled through one numeric representation.
Given a Integer-flag with key "integer-flag" and a default value "1"
When the flag was evaluated with details
Then the resolved details value should be "10"
And the error-code should be ""
And no exception should have been thrown

Scenario: A float flag resolves as a float
Given a Float-flag with key "float-flag" and a default value "0.1"
When the flag was evaluated with details
Then the resolved details value should be "0.5"
And the error-code should be ""
And no exception should have been thrown

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about integer precision? I think a lot of our SDKs support resolutions above 32 bit (though some like JS are practically capped at 2⁵³... it might be worth testing at least up to 2⁵³ -1

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fully agree, but for now, i would love to focus on the method, rather than the tests. We should agree and finalize the basic process. Afterwards we can add more tests to ensure compatibility

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added more cases on the side

Comment thread specification/assets/provider-tck/gherkin/lifecycle.feature Outdated
@toddbaert
toddbaert self-requested a review September 9, 2026 22:48
@toddbaert

Copy link
Copy Markdown
Member

I have to say, the Java impl is remarkably small and clean in Java. I think there's a lot of value here.

@toddbaert

Copy link
Copy Markdown
Member

I really like the idea, and the impl is clean (though TBH the issue and PR description is very wordy and maybe could be compressed and made less verbose) it took me a long time to get through 😅 .

I have 2 things I think I'd want to understand additionally:

  • extension: I'd want as an explicit Appendix F requirement: the single-init lifecycle should be open for authors to add their own scenarios/steps that run in the same "phase" (one backend start/teardown), so vendors like flagd extend the TCK rather than maintaining a parallel harness for their provider-specific features (fractional, for example). I think it'd be good to make this outcome-normative and per-language, since it's nearly free in godog/pytest-bdd/jest but the current Java annotation-driven suite can't add features/glue without redeclaring the whole set; so if it's not required, we will end up with suites that can't be easily extended. WDYT?

  • A report like you describe in Publishing provider conformance: a machine-readable report, and how to collect one from providers we do not host #424... I made a comment there, but I think we should consider bundling these (not sure).

@aepfli

aepfli commented Sep 10, 2026 •

Copy link
Copy Markdown
Member Author

I am in total favor for extensibility. This would even help our providers. We could try to define defaults in java, like a default resource path and a default step path, add those to the test, and have a simply way for the beginning. But also I am not sure, if this would be something which we should delay to the next version. For now, there is the idea, but do we need it immediately to ensure conformance. It sounds like a nice to have feature. A good iteration on the tck, i feel like it could bloat the current efforts. wdyt?

I measured what the Java classpath actually does here — the defaults idea works, with one catch

Four measurements against the real module:

absent @SelectClasspathResource hard discovery error
same directory name, two classpath roots additive — both scanned
same directory and filename one silently wins (test-classes beats the jar)
directory holding only a README resolves fine

The third is the catch. If the default resource path is just features/, a vendor adding features/errors.feature silently replaces the canonical file and the suite goes green having run theirs instead. So the default path has to be a separate one:

features/          canonical, TCK jar only
tck-extensions/    vendor features; ships with a README so the selector resolves

plus one extra glue package on the base suite (openfeature.tck.extensions) — an absent package is tolerated, so it costs nothing for anyone not using it.

Net cost: two lines in AbstractProviderTckTest and a README in the jar. A vendor then drops in a .feature file and a step class and writes zero annotations.

If it does land, I'd word it in Appendix F as an outcome rather than a mechanism — an adopter can add scenarios and steps that run in the same backend phase without redeclaring the canonical set — since it's pure convention in Java and Python, while Go and JS have no runtime scanning and need one registration callback.

Worth separating from the above either way: the suite should verify its canonical scenarios actually ran. That is not about extensibility — the same footgun already exists as cucumber.filter.tags, -k or testPathIgnorePatterns, and the file-shadowing result above makes it a concrete way to run fewer scenarios while reporting success.

// edit: flagd specifically is actually not a good reference example, as we are testing the evaluation engine seperately now. with really good tests. not sure if we want to have all of those tests executed during TCK evaluation. I see the benefit and the ease of running. But the more i think about, the TCK might be best to kept seperate as a verifiable unit, with whom nobody can mingle. Still torn, by both sides

aepfli added a commit to open-feature/js-sdk-contrib that referenced this pull request Sep 11, 2026
…odule

The feature files, canonical flag set and control-API document are owned by
open-feature/spec. Vendoring them here made this repository a second place the
definition of conformance could drift, which is precisely what the suite exists
to prevent. They are now a pinned submodule at libs/shared/provider-tck/spec
and the copies are gone.

Adopters are unaffected, and that is the constraint the change had to respect:
the rollup asset globs copy the artifacts out of the submodule and into the
published package, so installing @openfeature/provider-tck from npm still needs
no submodule and no particular repository layout. resolveAssetDir therefore has
to satisfy two layouts -- the packaged copy next to the bundle, and the
submodule under the library root -- and tries both. The spec calls the feature
directory `gherkin`; the package keeps the name the API talks about.

Contributors do need the submodule: without it no feature file loads at all.
`nx test` and `nx package` depend on a pullSpec target that initialises it, and
CI already checks out with `submodules: recursive`. Prettier is pointed at the
submodule instead of the old vendored paths so it never rewrites artifacts that
are consumed byte for byte by every language's TCK.

The .gitattributes normalising those files to LF goes with them; the equivalent
lives upstream, where the files now do.

Pinned to dfa1658 (open-feature/spec#423).

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Sep 14, 2026
lifecycle.feature was tagged @events, which is wrong in both directions.

Too strict: a stateless provider such as OFREP emits no events of its own
and cannot declare @events, yet it still initialises against a backend and
still owes the lifecycle contract.

Too lax: FeatureProviderStateManager emits PROVIDER_READY/PROVIDER_ERROR
around initialize for any provider, whether or not it is an EventProvider.
So a provider that does no initialisation of its own reaches READY exactly
as NoOpProvider would, and the readiness scenario passes vacuously.

Adds Capability.LIFECYCLE ("@lifecycle") -- performs an initialisation that
reaches its backend, with an observable outcome -- and re-vendors
lifecycle.feature verbatim from the spec assets, where the feature-level tag
is now @lifecycle (spec dfa16586, PR open-feature/spec#423).

flagd declares LIFECYCLE in both resolver modes: RPC does a round trip and
in-process syncs the whole ruleset during initialisation, so the scenarios
assert something real there.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Sep 14, 2026
…pec submodule

The Gherkin, the canonical flag set and the control API document are not Java
artifacts. They are language-agnostic definitions of the provider contract that
every language's TCK must agree on byte for byte, and they only lived in this
module because the proof of concept had to start somewhere.

They now live in open-feature/spec as Appendix F, under
specification/assets/provider-tck/, and are copied in from the `spec` git
submodule at generate-resources — the same mechanism tools/flagd-api-testkit
already uses for the flagd test harness. The copies are git-ignored and carry a
do-not-edit note; changes belong in the spec repo and arrive here by bumping the
submodule.

Consumers are unaffected: the artifacts are still packaged into the release JAR,
@SelectClasspathResource("features") still resolves, and nobody needs a submodule
of their own. Verified byte-identical after the round trip.

The in-memory CI job now checks out submodules, since without them there is no
suite to run.

DEPENDS ON open-feature/spec#423. The submodule is pinned to that PR's branch
commit rather than to a commit on the spec repo's main branch. That is reachable,
so CI can fetch it, but it must be re-pinned to main once #423 merges and before
this lands.

The pin is the branch tip rather than the first commit of that PR, so the copied
assets carry the `@numeric-coercion` vocabulary and the reserved-capability
control API this branch already uses. Verified byte-identical against the
artifacts this commit deletes.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Sep 14, 2026
A vendor with provider-specific features -- flagd's fractional targeting, a
proprietary evaluation mode -- had no way to test them inside this suite. The
only option was a second Cucumber runner of their own, which means a second
backend lifecycle to start and a second copy of this suite's configuration to
keep in step with it. Answers @toddbaert's review request on
open-feature/spec#423.

The suite now also selects the classpath directory tck-extensions/ and the glue
package openfeature.tck.extensions. An adopter writes two files and no
annotations:

    src/test/resources/tck-extensions/fractional.feature
    src/test/java/openfeature/tck/extensions/FractionalSteps.java

Their scenarios are discovered into the same suite and the same Cucumber engine,
and therefore the same @BeforeAll -- one backend lifecycle, one BackendControl.
The canonical steps are on the glue path too, so an extension scenario can open
with `Given a stable provider` and continue with whatever is specific to that
provider.

The extension directory is deliberately not features/ and not a subdirectory of
it. Measured on this module: two classpath roots holding the same directory are
scanned additively, but two holding the same directory *and* the same file name
are not -- one wins silently and the other file is never read, with
test-classes beating the jar. An adopter who put features/errors.feature in
their test resources would replace a canonical file with their own and watch the
suite report success having run theirs. A distinct name makes that collision
unreachable rather than documented.

The directory ships inside the jar holding nothing but a README, because a
@SelectClasspathResource naming a resource that exists on no classpath root is a
hard discovery error rather than an empty selection -- so an adopter who extends
nothing must still resolve it. Cucumber ignores files that are not .feature, and
tolerates a glue package that does not exist, so the unused extension point
costs an adopter nothing.

Also adds ProviderTck, which names every value the suite's annotations carry.
An annotation value has to be a compile-time constant, so an adopter who writes
a @ConfigurationParameter of their own cannot compute one; without the constants
they would restate our package name or our object factory as a string literal
that nothing keeps in step. Constant concatenation is legal in an annotation
value, so ProviderTck.ALL_GLUE + ",com.vendor.steps" is what they write instead.

The TCK's own fixture -- tck-extensions/extension-selftest.feature and a step
class in openfeature.tck.extensions -- is test-scoped, so it is not in the
released jar and cannot reach an adopter's run. It sits exactly where an
adopter's would, which is the only way to check the convention rather than
assert it about a path no build uses.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
@aepfli
aepfli force-pushed the feat/provider-tck-appendix branch from 25d000d to 4bab919 Compare September 15, 2026 19:57
@aepfli
aepfli force-pushed the feat/provider-tck-appendix branch from 4948f9b to ff68adb Compare September 16, 2026 19:31
aepfli added a commit to open-feature/js-sdk-contrib that referenced this pull request Sep 16, 2026
…odule

The feature files, canonical flag set and control-API document are owned by
open-feature/spec. Vendoring them here made this repository a second place the
definition of conformance could drift, which is precisely what the suite exists
to prevent. They are now a pinned submodule at libs/shared/provider-tck/spec
and the copies are gone.

Adopters are unaffected, and that is the constraint the change had to respect:
the rollup asset globs copy the artifacts out of the submodule and into the
published package, so installing @openfeature/provider-tck from npm still needs
no submodule and no particular repository layout. resolveAssetDir therefore has
to satisfy two layouts -- the packaged copy next to the bundle, and the
submodule under the library root -- and tries both. The spec calls the feature
directory `gherkin`; the package keeps the name the API talks about.

Contributors do need the submodule: without it no feature file loads at all.
`nx test` and `nx package` depend on a pullSpec target that initialises it, and
CI already checks out with `submodules: recursive`. Prettier is pointed at the
submodule instead of the old vendored paths so it never rewrites artifacts that
are consumed byte for byte by every language's TCK.

The .gitattributes normalising those files to LF goes with them; the equivalent
lives upstream, where the files now do.

Pinned to dfa1658 (open-feature/spec#423).

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Sep 16, 2026
lifecycle.feature was tagged @events, which is wrong in both directions.

Too strict: a stateless provider such as OFREP emits no events of its own
and cannot declare @events, yet it still initialises against a backend and
still owes the lifecycle contract.

Too lax: FeatureProviderStateManager emits PROVIDER_READY/PROVIDER_ERROR
around initialize for any provider, whether or not it is an EventProvider.
So a provider that does no initialisation of its own reaches READY exactly
as NoOpProvider would, and the readiness scenario passes vacuously.

Adds Capability.LIFECYCLE ("@lifecycle") -- performs an initialisation that
reaches its backend, with an observable outcome -- and re-vendors
lifecycle.feature verbatim from the spec assets, where the feature-level tag
is now @lifecycle (spec dfa16586, PR open-feature/spec#423).

flagd declares LIFECYCLE in both resolver modes: RPC does a round trip and
in-process syncs the whole ruleset during initialisation, so the scenarios
assert something real there.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Sep 16, 2026
…pec submodule

The Gherkin, the canonical flag set and the control API document are not Java
artifacts. They are language-agnostic definitions of the provider contract that
every language's TCK must agree on byte for byte, and they only lived in this
module because the proof of concept had to start somewhere.

They now live in open-feature/spec as Appendix F, under
specification/assets/provider-tck/, and are copied in from the `spec` git
submodule at generate-resources — the same mechanism tools/flagd-api-testkit
already uses for the flagd test harness. The copies are git-ignored and carry a
do-not-edit note; changes belong in the spec repo and arrive here by bumping the
submodule.

Consumers are unaffected: the artifacts are still packaged into the release JAR,
@SelectClasspathResource("features") still resolves, and nobody needs a submodule
of their own. Verified byte-identical after the round trip.

The in-memory CI job now checks out submodules, since without them there is no
suite to run.

DEPENDS ON open-feature/spec#423. The submodule is pinned to that PR's branch
commit rather than to a commit on the spec repo's main branch. That is reachable,
so CI can fetch it, but it must be re-pinned to main once #423 merges and before
this lands.

The pin is the branch tip rather than the first commit of that PR, so the copied
assets carry the `@numeric-coercion` vocabulary and the reserved-capability
control API this branch already uses. Verified byte-identical against the
artifacts this commit deletes.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Sep 16, 2026
A vendor with provider-specific features -- flagd's fractional targeting, a
proprietary evaluation mode -- had no way to test them inside this suite. The
only option was a second Cucumber runner of their own, which means a second
backend lifecycle to start and a second copy of this suite's configuration to
keep in step with it. Answers @toddbaert's review request on
open-feature/spec#423.

The suite now also selects the classpath directory tck-extensions/ and the glue
package openfeature.tck.extensions. An adopter writes two files and no
annotations:

    src/test/resources/tck-extensions/fractional.feature
    src/test/java/openfeature/tck/extensions/FractionalSteps.java

Their scenarios are discovered into the same suite and the same Cucumber engine,
and therefore the same @BeforeAll -- one backend lifecycle, one BackendControl.
The canonical steps are on the glue path too, so an extension scenario can open
with `Given a stable provider` and continue with whatever is specific to that
provider.

The extension directory is deliberately not features/ and not a subdirectory of
it. Measured on this module: two classpath roots holding the same directory are
scanned additively, but two holding the same directory *and* the same file name
are not -- one wins silently and the other file is never read, with
test-classes beating the jar. An adopter who put features/errors.feature in
their test resources would replace a canonical file with their own and watch the
suite report success having run theirs. A distinct name makes that collision
unreachable rather than documented.

The directory ships inside the jar holding nothing but a README, because a
@SelectClasspathResource naming a resource that exists on no classpath root is a
hard discovery error rather than an empty selection -- so an adopter who extends
nothing must still resolve it. Cucumber ignores files that are not .feature, and
tolerates a glue package that does not exist, so the unused extension point
costs an adopter nothing.

Also adds ProviderTck, which names every value the suite's annotations carry.
An annotation value has to be a compile-time constant, so an adopter who writes
a @ConfigurationParameter of their own cannot compute one; without the constants
they would restate our package name or our object factory as a string literal
that nothing keeps in step. Constant concatenation is legal in an annotation
value, so ProviderTck.ALL_GLUE + ",com.vendor.steps" is what they write instead.

The TCK's own fixture -- tck-extensions/extension-selftest.feature and a step
class in openfeature.tck.extensions -- is test-scoped, so it is not in the
released jar and cannot reach an adopter's run. It sits exactly where an
adopter's would, which is the only way to check the convention rather than
assert it about a path no build uses.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added 2 commits October 1, 2026 09:48
The feature files, the flag set they assume and the control API a backend under
test must expose, plus a nested Go module so a Go suite can depend on a revision
of them the way it depends on any other module.

These are the artifacts four conformance implementations execute. Their
normative description, Appendix F, follows in its own pull request: it is prose
and will take longer to review, while four languages are currently pinned to an
unmerged branch, so every rebase here invalidates them. The README carries the
capability vocabulary and the skipped-never-passed rule in the meantime, so the
tags the feature files use are not left undefined.

Experimental: the scenario set is a representative subset and the vocabulary may
still change. Tracked in #417.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
…se-please

Two packages. The specification keeps its bare vX.Y.Z tags and excludes the
assets path, so an asset-only change does not cut a spec release. The assets are
a Go component, tagged specification/assets/provider-tck/vX.Y.Z, which is the
shape go get needs -- without it a Go consumer can only name a pseudo-version.

Replaces #431, which configured the specification package alone.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
@aepfli
aepfli force-pushed the feat/provider-tck-appendix branch from ff68adb to 8374621 Compare October 1, 2026 07:53
@aepfli aepfli changed the title feat: add Appendix F, provider conformance (TCK) feat: add the provider conformance assets Oct 1, 2026
aepfli added a commit to open-feature/python-sdk-contrib that referenced this pull request Oct 1, 2026
The spec PR was split: the assets are open-feature/spec#423 and the appendix is
#439. This pin follows the assets, so appendix review no longer moves it. The
embedded artifacts are byte-identical across the move -- only the assets README
changed -- so no tally moves.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/js-sdk-contrib that referenced this pull request Oct 1, 2026
The spec PR was split: the assets are open-feature/spec#423 and the appendix is
#439. This pin follows the assets, so appendix review no longer moves it. The
assets consumed here are byte-identical across the move.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/java-sdk-contrib that referenced this pull request Oct 1, 2026
The spec PR was split: the assets are open-feature/spec#423 and the appendix is
#439. This pin follows the assets, so appendix review no longer moves it.

PINNED_DIGEST is unchanged, and that is the point: the digest covers flags/,
gherkin/ and openapi/, all byte-identical across the move. Only the assets README
differs, and it is not an artifact.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
aepfli added a commit to open-feature/go-sdk-contrib that referenced this pull request Oct 1, 2026
The spec PR was split: the assets are open-feature/spec#423 and the appendix is
#439. This pin follows the assets, so appendix review no longer moves it. The
embedded artifacts are byte-identical across the move, so the digest verified at
suite start is unchanged and no tally moves.

Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants