Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ labels: bug
- SDK: <!-- node / browser / react-native / flutter -->
- SDK version:
- OS / platform:
- Sidekick server version:
- Checkgate server version:

## Logs / error output

Expand Down
46 changes: 43 additions & 3 deletions .github/workflows/release-sdk-nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ jobs:
working-directory: sdks/nodejs
shell: bash
run: |
JS_FLAGS="--js native-binding.js --dts native-binding.d.ts"
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-musl" ]; then
# Satisfy host-side NAPI-RS linker check (will use real linker inside Docker)
sudo ln -s /usr/bin/true /usr/bin/aarch64-linux-musl-gcc
npx napi build --platform --release --target ${{ matrix.target }} --use-napi-cross
npx napi build --platform --release --target ${{ matrix.target }} --use-napi-cross $JS_FLAGS
elif [ "${{ matrix.cross }}" = "true" ]; then
npx napi build --platform --release --target ${{ matrix.target }} --use-napi-cross
npx napi build --platform --release --target ${{ matrix.target }} --use-napi-cross $JS_FLAGS
else
npx napi build --platform --release --target ${{ matrix.target }}
npx napi build --platform --release --target ${{ matrix.target }} $JS_FLAGS
fi

- name: Upload artifact
Expand All @@ -90,6 +91,23 @@ jobs:
path: sdks/nodejs/${{ matrix.artifact }}
if-no-files-found: error

# The generated JS dispatch loader (native-binding.js/.d.ts) is
# platform-agnostic — it bundles runtime branches for every supported
# OS/arch/libc, not just the one that produced it (verified by
# inspecting its output). Upload it once, from whichever build happens
# to run first, for the publish job to assemble alongside all six
# `.node` binaries. Named `node-*` so it's picked up by the same
# download pattern as the per-target artifacts below.
- name: Upload JS dispatch loader
if: matrix.target == 'x86_64-unknown-linux-gnu'
uses: actions/upload-artifact@v4
with:
name: node-js-binding
path: |
sdks/nodejs/native-binding.js
sdks/nodejs/native-binding.d.ts
if-no-files-found: error

# ---------------------------------------------------------------------------
# Publish Job — waits for all native builds above
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -125,11 +143,33 @@ jobs:
ls -l *.node
exit 1
fi
if [ ! -f native-binding.js ] || [ ! -f native-binding.d.ts ]; then
echo "Error: native-binding.js/.d.ts missing — index.js requires these at runtime."
exit 1
fi

- name: Install dependencies
working-directory: sdks/nodejs
run: npm install

# Actually require() the assembled package end to end — this is the
# exact failure mode that shipped silently before: index.js requiring a
# file that doesn't exist in the real multi-platform output. A missing
# native-binding.js, a stale `require('./index.node')`, or a renamed
# export would all be caught here instead of surfacing only when a
# consumer runs `npm install @checkgate/node`.
- name: Smoke test the assembled package
working-directory: sdks/nodejs
run: |
node -e "
const { CheckgateClient } = require('./index.js');
if (typeof CheckgateClient !== 'function') throw new Error('CheckgateClient export missing');
const client = new CheckgateClient({ serverUrl: 'http://localhost:1' });
if (typeof client.isEnabled !== 'function') throw new Error('isEnabled method missing');
if (typeof client.connect !== 'function') throw new Error('connect method missing');
console.log('Smoke test passed — native binding resolved and CheckgateClient constructed.');
"

- name: Set version from tag
working-directory: sdks/nodejs
run: |
Expand Down
97 changes: 97 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Changelog

All notable changes to Checkgate are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [0.1.18] - 2026-07-04

### Added

- **Prerequisite (dependent) flags** — a flag can require another flag to be enabled, or resolved to a
specific value, before its own rules/rollout are considered. Evaluated recursively (a prerequisite
can itself have prerequisites) with a depth guard that fails closed on cycles or misconfigured
chains. Lives in the shared evaluation core, so every SDK supports it automatically.
- **Weighted multivariate rollouts** — a `variants` field distributes traffic across multiple values
by weight (e.g. a 60/30/10 A/B/C split), independent of the on/off rollout gate. The foundation for
A/B testing.
- **`getValue`/`getVariant` in every SDK wrapper** — the underlying bindings already supported
multi-variant flags, but the public Node, Web, React Native, and Flutter wrappers only exposed
`isEnabled`. All four now expose the full evaluation surface.
- **SDK impression reporting** — all SDKs asynchronously batch and report evaluation events, feeding
the dashboard's impression stats and evaluation stream. Off by default; user attributes are never
sent unless `sendEvaluationContext` is enabled.
- **Numeric targeting operators** — `greater_than`, `greater_than_or_equal`, `less_than`,
`less_than_or_equal`, alongside the existing string operators.
- **SDK resilience** — exponential backoff with jitter on SSE reconnect (replacing a fixed retry
delay); flag-change listeners (`onChange`) with bootstrap/reconnect-resync suppression so only
genuine live deltas fire; offline persistence via a pluggable storage adapter (hydrate on cold
start, persist on bootstrap/delta); and an HTTP poll fallback (`GET /flags/snapshot`) for
environments where SSE can't be established (e.g. a proxy blocking long-lived connections).
- **Unified `connect()`/ready semantics** — a new server-emitted `ready` SSE event gives every SDK a
consistent way to know when the initial flag set has loaded, instead of each platform inferring it
differently.
- **Flag lifecycle hygiene** — tags, an owner email, and archival (soft-delete, reversible, zero
evaluation impact). Kept as dashboard-only metadata on a `FlagWithMetadata` wrapper so it never
flows into the evaluation core or the SSE/`/flags/snapshot` wire format.
- **Cross-environment diff** — a "Compare environments" dashboard page showing flags that exist in
only one environment, or differ in evaluation-relevant fields (rollout, rules, variants,
prerequisites), with a one-click sync action per flag.
- **Scoped personal access tokens** — user-owned, revocable API credentials for CI/CD, Terraform, and
scripts, as an alternative to the always-admin-equivalent SDK key. A token acts as its owning user
(same role, same project memberships) and can be capped to `read_only`. Tokens are SHA-256 hashed
at rest, support optional expiry, and are strictly self-service (list/create/revoke your own only).
- **Change requests (approval workflow)** — a per-environment `require_approval` toggle. When set, a
flag `PATCH` is captured as a pending change request instead of applying immediately, and a
*different* editor/admin must review it. Approve applies the original patch against the flag's
current state; reject (with an optional reason) or withdraw never touch the flag.

### Changed

- `evaluate`/`evaluate_variant` in the core evaluator now take a `&FlagStore` parameter to support
recursive prerequisite lookups. Internal API change — all four SDK bindings were updated
accordingly; public SDK surfaces are unaffected.

### Fixed

- `time::OffsetDateTime` fields in `impressions.rs`, `keys.rs`, and `users.rs` were serializing in a
proprietary, non-RFC-3339 format that JavaScript's `Date` cannot parse, despite doc comments
claiming "ISO-8601." Corrected to genuine RFC 3339 output across all affected endpoints.
- Node.js SDK: `eventsource` v4's named export and dropped `headers` option meant the Bearer token
was silently omitted from SSE reconnect requests. Fixed via a custom `fetch` hook.
- Dashboard `Environments.tsx` called bare `/api/environments...` routes that don't exist on the
server (the real routes are nested under `/api/projects/{project_id}/environments...`), causing
"Server returned 405" on environment creation and silent failures on delete/set-default.
- Removed stale, git-tracked copies of `sdks/react-native/rust-core/` — a generated build artifact
that had drifted out of sync with `core/`. Now gitignored; regenerated fresh before every release.
- **Node.js SDK packaging** — `index.js` (the hand-written wrapper) and `napi build --platform`'s
auto-generated multi-platform dispatch loader both wrote to the same filename, so whichever ran
last won; the checked-in wrapper always won in practice, and it `require()`d a single-file
`index.node` that never exists in the real per-platform release output
(`checkgate.darwin-x64.node`, `checkgate.linux-x64-gnu.node`, etc.). Every install of
`@checkgate/node` would have thrown `Cannot find module './index.node'`. Pre-existing since at
least v0.1.17 and never caught because nothing in CI actually `require()`d the assembled package.
Fixed by generating the dispatch loader to a separate `native-binding.js`/`native-binding.d.ts`
(via `napi build`'s `--js`/`--dts` flags) that `index.js` requires instead, and by adding a real
`require()` smoke test to the release workflow so this class of bug fails loudly in CI instead of
shipping silently.

### Security

- Personal access tokens are SHA-256 hashed at rest (SDK keys remain plaintext, by contrast, since
they're a different, coarser-grained credential class), scoped to the owning user's actual role and
project memberships rather than being blanket admin-equivalent, and a `read_only` token cannot mint
a `read_write` replacement for itself.
- Change-request self-approval is rejected — a reviewer other than the requester must approve.

## [0.1.17] - 2026-06-14

- Audit logs, user segmentation, webhooks with HMAC signing, time-based scheduled flag changes, and
live SSE connection monitoring (SDK Health).

## [0.1.0] - 0.1.16

See individual SDK changelogs (e.g. `sdks/flutter/dart/CHANGELOG.md`) and `docs/roadmap.md` for
earlier history: initial local-evaluation core, multi-variant flags, editor RBAC, projects/multi-
tenancy, impression tracking, and security hardening.
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,15 @@ Prefix with `fix:`, `feat:`, `docs:`, `refactor:`, `test:`, or `ci:`.
cargo test --workspace

# Node.js SDK (requires a build first)
cd sdks/nodejs && npm install && npm run build
cd sdks/nodejs && npm install && npm run build:debug
```

> All four build scripts (`build`, `build:debug`, `build:zig`, `build:cross`) generate the
> platform-specific `checkgate.*.node` binary plus a `native-binding.js`/`native-binding.d.ts`
> dispatch loader — never `index.js`/`index.d.ts`. Those two are hand-written and wrap the native
> binding (see `sdks/nodejs/index.js`), which `require()`s `./native-binding.js`; they're not
> NAPI-RS auto-generated files, and no build script touches them.

## Submitting a PR

- Fill in the pull request template.
Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ members = [
resolver = "2"

[workspace.package]
version = "0.1.17"
version = "0.1.18"
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,40 @@ It is proudly built in Rust and ships with native SDKs for Node.js (NAPI), brows

---

## Screenshots

*Dashboard shown with example data for a fictional company, Vantage Robotics.*

**Dashboard overview** — flag counts, rollout status, and recent activity at a glance.

![Dashboard overview](assets/screenshots/01-dashboard.png)

**Feature flags** — tags, types, rollout percentage, and one-click enable/disable per environment.

![Feature flags list](assets/screenshots/02-feature-flags.png)

**Flag editor** — targeting rules, tags, ownership, and prerequisite (dependent) flags.

![Flag editor](assets/screenshots/03-flag-editor.png)

**Change requests** — require a second reviewer before a flag change takes effect in sensitive environments; self-approval is blocked.

![Change requests](assets/screenshots/04-change-requests.png)

**Cross-environment diff** — see what's different between environments before promoting, with a one-click sync.

![Compare environments](assets/screenshots/05-environment-diff.png)

**Personal access tokens** — scoped, revocable API credentials for CI/CD and Terraform, as an alternative to admin-equivalent SDK keys.

![Personal access tokens](assets/screenshots/06-settings-tokens.png)

**Environments** — isolate configuration across Production, Staging, UAT, and Development, with per-environment approval gates.

![Environments](assets/screenshots/07-environments.png)

---

## Documentation

| Topic | Link |
Expand Down
Binary file added assets/screenshots/01-dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshots/02-feature-flags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshots/03-flag-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshots/04-change-requests.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshots/05-environment-diff.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshots/06-settings-tokens.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/screenshots/07-environments.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading