Skip to content

Publishing provider conformance: a machine-readable report, and how to collect one from providers we do not host #424

Description

@aepfli

Follow-up to #417, which asks in its open questions: "Who owns conformance claims? Is there a badge, a report format, a listing on openfeature.dev? Or is passing simply its own reward for now?" This issue is that question, split out so it can progress on its own timeline — #417 is already carrying Appendix F, four language implementations and a findings table.

This is a design discussion, not a proposal to implement. The positions below are opening arguments, and the two marked as traps are the ones I would most like challenged.

The opportunity

The provider conformance suite (Appendix F, #423) now has implementations in four languages, each of which already computes exactly the data a public conformance report would need:

  • the capability set a provider declares
  • a per-scenario outcome, with a reason string attached to every skip

What is missing is an agreed report format and a way to publish and collect reports. That would let openfeature.dev show, for any provider, what it actually supports — replacing "implements the provider contract" as an unverified claim with something a user can look up before choosing a provider.

The awkward part

Not every provider lives in an SDK-contrib repo. Many are in vendor organisations, on their own release cadence, and OpenFeature cannot run their suites — the backends and credentials are not ours. So any design has to work for providers we do not host and cannot execute.

Three problems, hardest last

1. Comparability

A report means nothing unless you know it answers the same question as the report beside it. Each report must carry the TCK implementation, its version, the spec revision its assets came from, and a digest of the Gherkin and canonical flag set actually executed. Without that last one, two reports can differ for unknowable reasons — the exact failure this whole effort exists to prevent.

These are not independent facts. The spec revision is a property of the TCK artifact, not something the report asserts separately: a given TCK build vendors exactly one revision. So they collapse into a single object rather than sitting in a separate assets block.

The revision and the digest are still worth carrying together, because they do different jobs. The revision is an assertion; the digest is a check. A recorded revision can be wrong — someone edits a vendored feature file and the label still points at the original commit. The digest catches precisely that, which is the drift this whole effort exists to prevent. They are a self-checking pair, not duplicates.

This has been settled at the source rather than in the schema. The decision is that no TCK vendors the conformance assets: every implementation consumes them from this repository as a git submodule, so the spec revision is always recorded by the submodule pin. Java already did this; Go, Python and JavaScript are being converted.

Two consequences for this issue:

  • specRevision is never unknown and never has to be reconstructed — it is whatever the submodule points at, available to the emitter for free.
  • An adopter of a TCK still needs no submodule of their own. Each implementation packages the assets into its published artifact (embedded in the Go package, in the Python wheel, in the JAR, in the npm package), so the submodule is a concern for TCK contributors only. That property is worth stating in the schema documentation, because it is what stops "conformance report" from implying "clone two repositories".

2. Trust

A JSON file in a vendor repository is a claim, not a fact. Three options:

Approach Verdict
OpenFeature re-runs every suite centrally does not scale — needs each vendor's backend and credentials
Naked self-report worthless the moment anything is contested
Self-report with provenance the only one that scales

Provenance meaning: the report is emitted only by CI, and carries the workflow run URL, commit SHA, and optionally a sigstore / GitHub OIDC attestation. This is what the rest of the supply-chain ecosystem already does for build artifacts.

Whatever we choose, the page should say plainly what it is showing: attested self-reports, not audits.

3. Discovery

Do not crawl. Suggested shape:

  • a registry of pointers in an OpenFeature-owned repo — provider → report URL, added once by a vendor PR
  • vendors self-publish the report to that URL (release asset, GitHub Pages, wherever)
  • a scheduled job fetches every registered URL, validates against the schema, and rebuilds the page

The human-reviewed list stays small and stable; report content refreshes without anyone raising a PR. A /.well-known/openfeature-conformance.json convention could layer on later for vendors who prefer it.

Trap 1: two outcomes is not enough

A report needs four outcomes:

Outcome Meaning
passed ran, conformed
failed ran, did not conform
not-declared the provider says it does not do this
not-applicable the language cannot express the question

That fourth category is not hypothetical — it appeared while building the JavaScript implementation. @strict-numeric-typing asserts that a provider reports TYPE_MISMATCH rather than narrowing 0.5 to 0 when a float flag is requested as an integer. JavaScript has no integer type: typeof 10 and typeof 0.5 are both 'number', and the Evaluation API exposes only getNumberDetails. Requesting float-flag as an Integer is indistinguishable from requesting it as a Float, so no JavaScript provider can ever satisfy that scenario — not through a defect, but because the distinction does not exist in the language.

Collapse not-applicable into not-declared and the overview page will permanently show every JS provider as missing a capability no JS provider can have. That is a defect in the report rendered as a defect in the provider.

(This also feeds back into #417's open question about whether @strict-numeric-typing should be language-scoped at the specification level.)

Trap 2: never compute a conformance percentage

This is the one I feel strongest about.

A single score rewards declaring fewer capabilities. Undeclare @stale and your percentage goes up. That is conformance theatre, and it exactly inverts the incentive the suite exists to create.

Instead: a matrix of provider × capability, four distinct states, no aggregate number, and the skip reason shown. "This provider does not support configuration-change events" is genuinely useful to someone choosing a provider — hiding it behind a green 100% is actively harmful. not-declared should be as visible as failed.

Sketch of a report

Illustrative only — the field names are the least interesting part of this issue:

{
  "schemaVersion": "1",
  "provider": { "name": "flagd", "version": "0.14.1", "language": "java", "configuration": "rpc" },
  "sdk": { "name": "dev.openfeature:sdk", "version": "1.17.0" },
  "tck": {
    "implementation": "java-sdk-contrib/tools/provider-tck",
    "version": "0.1.0",
    "specRevision": "42b047b9",
    "gherkinDigest": "sha256:..."
  },
  "backend": { "description": "flagd-testbed v3.10.1", "controlApi": "launchpad" },
  "provenance": { "runUrl": "https://github.com/.../actions/runs/123", "commit": "abc123" },
  "capabilities": {
    "events": "passed",
    "stale": "not-declared",
    "strict-numeric-typing": { "state": "not-applicable", "reason": "no integer type in this language" }
  },
  "scenarios": [
    { "feature": "errors", "name": "Requesting the wrong type returns the code default", "outcome": "passed" }
  ]
}

Note provider.configuration: one provider can produce several reports. flagd has two resolvers with genuinely different conformance — its Go RPC resolver does not emit PROVIDER_STALE while its in-process one does (go-sdk-contrib#939). A report keyed only on provider name would have to pick one and lie about the other.

Suggested phasing

  1. Schema in this repo, next to the Gherkin — same kind of artifact, must version alongside it.
  2. Emitters in each language TCK. The data already exists; this is a JSON writer.
  3. Registry + aggregator, designed once two or three real reports exist.
  4. The page, last.

Steps 1–2 are worth doing soon because they are cheap and they make step 3 designable against real data. Steps 3–4 should not be designed in the abstract.

Questions

  1. Should every TCK be required to record the spec revision it vendored? Settled: assets are consumed from this repository via git submodule in every implementation, so the revision is always recorded by the pin. The open part is narrower — should the report carry the submodule commit, a spec release tag, or both, given that a commit is precise but a tag is what a human can reason about?
  2. Is not-applicable the right fourth state, or should the specification instead scope capabilities per language so the situation cannot arise?
  3. Registry-of-pointers, or a well-known URL convention, or both?
  4. How much provenance is proportionate — a CI run URL, or a full signed attestation?
  5. Does a report expire? A report generated against a two-year-old TCK revision is not obviously worth displaying, but silently dropping it is its own kind of dishonesty.
  6. Who owns the registry and the aggregator — this repo, openfeature.dev, or something new?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions