Skip to content

feat(migrate-eppo): phase 2 code-migration — client SDKs, struct keys, resolve modes#18

Open
fabriziodemaria wants to merge 7 commits into
feat/migrate-eppofrom
feat/migrate-eppo-phase2
Open

feat(migrate-eppo): phase 2 code-migration — client SDKs, struct keys, resolve modes#18
fabriziodemaria wants to merge 7 commits into
feat/migrate-eppofrom
feat/migrate-eppo-phase2

Conversation

@fabriziodemaria
Copy link
Copy Markdown
Member

@fabriziodemaria fabriziodemaria commented Jun 1, 2026

Summary

Stacked on #17 (Phase 1). Hardens the Phase 2 (code migration) half of the Eppo→Confidence skill after test-running it against real Eppo example apps across all four resolve modes. The detection front-end already worked; these commits fix the transform half and add migrated fixtures.

What changed

  • Client-SDK vs server-SDK transforms. The transform table only modeled server SDKs (per-call context). Added a client-SDK branch: ambient context via setEvaluationContext/setEvaluationContextAndWait, no per-call context arg. SDK side is classified from the detected Eppo package.
  • Struct / dot-notation flag keys + Phase-1→Phase-2 handoff. Confidence flags are structs, read by flag.property. Phase 1 now records a deterministic resolve path (BOOLEAN→.enabled, other scalars→.value, JSON→struct shape) on each flag's plan entry, which Phase 2 consumes verbatim.
  • Legacy get_assignment detection — added to the scan grep, with handling for its inverted (subjectKey, flagKey) arg order and untyped return.
  • Remove Eppo-side readiness scaffolding (server AND client: Android Handler.postDelayed, Go Initialized() channel, Java buildAndInit(), Python wait_for_initialization()), and surface flags referenced in code but never migrated in Phase 1 instead of inventing a path.
  • Honor the "prefer local resolve" policy operationally. Step 2 routes to getLocalResolveIntegrationGuide for backend Java/Go/JS/Rust targets, else the remote provider guide.
  • Three resolve modes, not a local/remote binary + a fourth server-precomputed variant for React/Next.js. In-process (backend Java/Go/JS-Node/Rust WASM), cached client (mobile/web), server-precomputed (RSC: server resolves, client reads offline), remote (Python/Ruby/.NET). Any resolve-mode change is signaled with precise consequences.
  • Shared core gains the two Confidence-wide transform truths (struct dot-paths; client ambient vs server per-call) so PostHog benefits too.

Validation against real Eppo apps (per resolve mode)

Each example was migrated following the skill; gaps found were fixed and re-verified. Migrated output is committed under skills/migrate-eppo/test-fixtures/phase2-examples/ (source-only, no build artifacts).

Mode Source Confidence target Gaps fixed Verified
cached client android-sdk-kotlin-example (Kotlin) Android provider client ambient context, struct keys, readiness removal doc-verified (no Android SDK)
server-precomputed nextjs-precomputed-client (Next.js/React) openfeature-server-provider-local (react-server/react-client) precompute→ConfidenceProvider+useFlag, getBoolAssignment grep, RSC awareness, drop exposure bridge tsc --noEmit + next lint clean
in-process sdk-test-data Go + Java relays Go / Java local-resolve provider Go PascalCase GetBoolAssignment, Java getDoubleAssignment/getJSONStringAssignment, per-lang accessor shapes, bandits BLOCKED Java ./gradlew compileJava BUILD SUCCESSFUL; Go doc-verified
remote sdk-test-data Python relay spotify-confidence-sdk OpenFeature provider numeric→get_float_value, remote init (api.set_provider, no wait), in-process→remote signal py_compile + API existence vs spotify-confidence-sdk==3.0.1

Rust was considered but Eppo ships no Rust SDK, so there is no source to migrate from.

Test plan

  • Re-ran Phase 2 against the Kotlin example following the updated skill — all five originally-found gaps now resolve correctly.
  • JS/React: migrated the Next.js precomputed example end-to-end; tsc + lint clean against provider 0.14.1.
  • Go/Java: migrated both server relays; Java compiles clean against com.spotify.confidence:openfeature-provider-local:0.15.0; Go doc-verified.
  • Python: migrated the server relay; byte-compiles and all accessors/imports verified against the installed spotify-confidence-sdk.
  • Committed migrated fixtures + README mapping each to its upstream source, target SDK, and verification method.
  • PostHog regression pass.

Made with Cursor

fabriziodemaria and others added 7 commits June 1, 2026 13:25
Phase 2 code migration previously only modeled server SDKs. Add:
- client vs server SDK classification (ambient vs per-call context)
- struct/dot-notation resolve paths with a Phase-1->Phase-2 property
  handoff (BOOLEAN -> .enabled, other scalars -> .value)
- legacy get_assignment detection (inverted arg order, untyped return)
- removal of Eppo-side readiness scaffolding (e.g. Android postDelayed)
- surfacing flags referenced in code but never migrated in Phase 1

Shared core gains the two Confidence-wide transform truths so PostHog
benefits too.

Co-authored-by: Cursor <cursoragent@cursor.com>
Honor the "prefer local resolve" policy operationally: Phase 2 Step 2
now picks local resolve (getLocalResolveIntegrationGuide) for backend
Java/Go/JS/Rust targets and remote resolve otherwise, instead of always
fetching the remote provider guide.

Also compare source vs target resolve mode and surface a notice when the
migration shifts evaluation between in-process (local) and service-call
(remote) — it changes latency, caching/freshness, and offline behavior.
Eppo declares its source mode as local (in-process UFC eval), so Eppo ->
Confidence remote (client SDKs, or Python/Ruby/.NET backends) now warns
local -> remote.

Co-authored-by: Cursor <cursoragent@cursor.com>
Mobile/web client SDKs are cached-client (backend resolves, device
caches resolved values for fast offline reads) - not a network call per
read. JS splits by surface: Node server can be in-process WASM or
remote, browser is cached-client (the WASM provider is server-only).

Replace the local/remote binary with three target modes (in-process,
cached client, remote) and rewrite the change-signal to describe what
actually shifts per transition (where eval happens, per-read latency,
freshness/refetch, cold-start defaults, ruleset exposure). Eppo now
declares per-surface source modes: backend = in-process, client =
on-device eval.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a server-precomputed resolve mode and a React/Next.js (RSC) transform
branch so the Eppo precompute pattern (server getPrecomputedConfiguration
+ client offlinePrecomputedInit) maps onto Confidence's local-resolve
ConfidenceProvider + useFlag flow.

- Scan: match JS method-name variants (getBoolAssignment, getJsonAssignment)
  and detect the precomputed pattern (getPrecomputedConfiguration,
  offlinePrecomputedInit, getPrecomputedInstance)
- Transform: map server init -> createConfidenceServerProvider, precompute
  string -> ConfidenceProvider context, client reads -> useFlag, and drop
  the assignment-logger/custom-event exposure bridge
- Resolve mode: classify server-precomputed and signal it as preserved
  (no local<->remote change) when migrating to the React provider

Co-authored-by: Cursor <cursoragent@cursor.com>
Validated the code migration against the Eppo Go and Java server relays
and closed the gaps that surfaced:

- Scan: case-insensitive grep + a value-type-to-spelling table so we catch
  Go PascalCase (GetBoolAssignment), Java getDoubleAssignment (numeric)
  and getJSONStringAssignment (JSON-as-string), not just the JS/Python forms
- Transform: document the per-language accessor shape (Go ctx-first/no-get
  prefix; Java getDoubleValue/getObjectValue) and drop the gson re-parse for
  JSON since getObjectValue returns a structured value
- Readiness scaffolding removal now covers server init too (Go Initialized()
  channel wait, Java blocking buildAndInit())
- Bandits (getBanditAction/BanditResult) flagged as BLOCKED with no
  Confidence equivalent

Co-authored-by: Cursor <cursoragent@cursor.com>
Validated the code migration against the Eppo Python server relay (the only
remote target — Confidence has no local resolve for Python) and closed the
gaps that surfaced:

- Add Python to the value-type-to-accessor table; numeric maps to
  get_float_value (not getNumberValue/getDoubleValue), JSON to
  get_object_value, all snake_case get_<type>_value
- Document the remote init shape: api.set_provider(
  ConfidenceOpenFeatureProvider(Confidence(client_secret=...))) with NO
  set_provider_and_wait, and delete Eppo's wait_for_initialization()

This exercises the in-process -> remote resolve-mode change signal, which
the core already models for backend Python/Ruby/.NET targets.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add the migrated (post-Confidence) output for each validated resolve mode,
plus a README mapping every fixture to its upstream Eppo source repo, the
Confidence SDK it targets, and how it was verified:

- go-server / java-server  — in-process local resolve (Java compiled clean)
- python-server            — remote target (in-process -> remote signal)
- nextjs-precomputed       — server-precomputed React/RSC (tsc + lint clean)

Source-only (no build artifacts); these pin expected transform output so
regressions in the skill's mapping tables are easy to catch.

Co-authored-by: Cursor <cursoragent@cursor.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.

1 participant