Skip to content

Dev - #20

Merged
akosidencio merged 14 commits into
mainfrom
dev
Oct 18, 2026
Merged

Dev#20
akosidencio merged 14 commits into
mainfrom
dev

Conversation

@akosidencio

Copy link
Copy Markdown
Member

Summary

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Docs / config
  • CI / build

Testing

  • cargo test --workspace passes
  • Manually tested (describe below)

Checklist

  • No breaking changes to the SDK public API (or noted below)
  • Relevant docs updated

…er flag events

- SDK keys ship in browser and mobile bundles but were accepted as workspace
  admins everywhere. They now only reach the SDK routes (/stream,
  /flags/snapshot, reading flags, impressions/events) for their bound
  environment; admin/editor gates and handler fallbacks reject them.
- Impression and event ingest authorize the caller: an SDK key for that
  environment, or a user with write access (no viewers, no read_only PATs).
- Publish through a redis ConnectionManager so a broken connection is
  replaced. A publish that still fails is delivered to local SSE clients and
  a RESYNC broadcast is retried until Redis is back; SSE handlers close their
  streams on RESYNC, and the subscriber triggers one after resubscribing.
- Flags carry a revision from a global sequence, bumped by trigger on every
  update. UPSERT/DELETE events and the SSE bootstrap include it so clients can
  drop events that arrive out of commit order.
- The default-environment index is per project, and switching the default
  clears the old one first (it returned 500).
- Integration and SSE e2e suites use an admin PAT for management calls and
  cover the new SDK-key scope, ingest authorization, default switching,
  revisions, and publisher/subscriber recovery.
…nalytics

Node, Web and Flutter (React Native follows with its native-module commit):

- A (re)connect no longer clears the live flag set. The replay is staged and
  swapped in on "ready", so isReady() never sits over an empty store and a
  replay cut short by a dropped stream is discarded. On reconnect, listeners
  fire for flags that actually changed.
- Change events carrying a revision are ignored when older than the one
  already applied (and a deleted flag only returns with a newer revision).
  Events from older servers without revisions still apply.
- Impressions carry evaluated_at and conversions occurred_at from the moment
  they happen, so independently flushed batches keep their real order for
  experiment attribution.
UpdateFlag encodes Flag with omitempty, so an emptied rule list, variants,
prerequisites, tags, or a removed description were left out of the PATCH and
kept their stored values. ReplaceFlag sends every clearable field explicitly
([] / null). Terraform and the Kubernetes operator now use it. UpdateFlag stays
a partial update and documents that it cannot clear fields.

Docs no longer offer SDK keys for these tools; they need a read_write PAT.
The server pages GET /environments/{env}/flags (200 by default), and both
clients made a single request, silently hiding flags beyond the first page
from dashboard search/comparison and from generated CLI types. Both now page
at the server maximum (500) until a short page.
…angelog

- SDK keys only reach the SDK endpoints for their environment; automation
  (Terraform, the operator, scripts, CI) uses personal access tokens.
- Ingest authorization and the evaluated_at/occurred_at fields.
- SSE events carry a revision; clients stage reconnect replays until "ready",
  and the server closes streams to force a resync after missed updates.
… bindings

The package never compiled or linked its Rust library, and nothing native
called installCheckgateJSI(), so connect() could not work on a device.

- Android: a Java module + package (autolinked) whose synchronous install()
  hands the JS runtime to a JNI adapter; Gradle runs cargo ndk for the app's
  ABIs before CMake, which links the static library with ReactAndroid::jsi
  (prefab enabled, abiFilters matched to the Rust ABIs).
- iOS: an ObjC++ module with the same install(), and a CocoaPods build phase
  that compiles the engine for the active SDK/architectures and links it.
- JS calls NativeModules.Checkgate.install() when the JSI global is missing.
- bundle-rust.js resolves workspace-inherited manifest fields and makes the
  packed crate its own workspace root, so it builds from node_modules.
- Same staged-bootstrap, revision and timestamp changes as the other SDKs.
- CI packs the package, builds it outside the repo, and links an Android
  libcheckgate.so for every ABI with no undefined symbols.

Verified: an RN 0.76.9 app consuming the packed tarball assembles with
libcheckgate.so in the APK. Not yet run on a device or built for iOS.
- Rust: rustls 0.23.46 (RUSTSEC-2026-0346/0347), quinn-proto 0.11.19
  (RUSTSEC-2026-0338..0342), xxhash-rust 0.8.19, and the yanked chacha20 and
  spin releases. `cargo deny check` and `cargo audit` are clean.
- Web/RN SDKs: vitest 4.1.12 (with @vitest/mocker), postcss 8.5.30 and
  source-map-js 1.2.2 via vite.
- RN SDK: stop auto-installing the react/react-native peer dependencies for
  local development (.npmrc legacy-peer-deps). Tests stub react-native, and
  the peer tree was the only source of the shell-quote, image-size,
  browserslist and baseline-browser-mapping alerts.
- Node SDK: js-yaml 4.3.2.
- Docs: postcss 8.5.30 and source-map-js 1.2.2.
- Terraform provider: grpc 1.83.1.
…gs per project

- Segment PATCH used COALESCE for the description, so it could never be
  cleared. `"description": null` now clears it; an omitted field still keeps
  the stored value.
- The Go client gains ReplaceSegment (null description, [] rules when empty);
  the Terraform segment resource uses it, so a description or rules removed
  from configuration are cleared on the server.
- Environment slugs were UNIQUE across all projects (a constraint older than
  projects), so a second project could not create its own "production". The
  constraint is now a (project_id, slug) unique index.
…in bootstrap

- A segment create/update/delete changes the expanded payload of every flag
  that uses it, but republished them under the old revision, so SDKs (which
  accept equal revisions) could not tell an old expansion from the new one.
  The referencing flags now get a new revision inside the segment's
  transaction, and their events are rendered from that same snapshot.
- A client that bootstrapped after a delete had no revision for the deleted
  key, so a delayed event from before the delete brought the flag back. A
  trigger records each deleted flag's last revision in flag_tombstones (kept
  for a day), and the bootstrap replays them as DELETE events.
- The bootstrap reads segments, flags, and tombstones in one REPEATABLE READ
  transaction, so each revision matches the content it is sent with.
…lied

_applySnapshot() fired onChange while it was still applying flags, so a
listener evaluating a flag could see some of its prerequisites updated and
others not. All four SDKs now apply every upsert, deletion, and revision (and
persist the cache) before notifying. Tests also cover a bootstrap tombstone
rejecting a delayed older event.
…ment

A segment change re-expands the flags that reference it from inside its
transaction, but flag writes and segment writes both took only a FOR SHARE
lock on the environment, so they never waited for each other. A flag created
(or re-pointed at the segment) while a segment change was in flight missed
its scan, expanded against the old, still-visible segment after committing,
and never received a corrective event.

Segment writes now lock the environment row FOR NO KEY UPDATE, which excludes
the FOR SHARE every flag write takes: either the flag write commits first and
the segment's scan includes it, or it waits and expands against the committed
segment. apply_patch_in_tx takes the share lock itself (before the flag row
lock), so change-request approvals, which did not lock the environment, are
covered too. The SSE e2e test holds an in-flight flag write open and checks
that the segment edit waits and republishes that flag with the new rules.
@akosidencio
akosidencio merged commit 34b0987 into main Oct 18, 2026
34 checks passed
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