feat(devtools): feature flag control from the Flags tab - #36
Open
mono424 wants to merge 1 commit into
Open
Conversation
Flags could only be changed with `spky flag` from a terminal holding root credentials. This adds a Flags tab to the DevTools extension with two capabilities that have deliberately different blast radii: - Local overrides force a variant in one browser. No auth, no network, works signed out and offline. Persisted to page-origin localStorage and exposed as client.setFeatureOverride/clearFeatureOverrides. - Remote changes flip a flag's `enabled` bit or edit an allowlist for every user, and require admin rights. sp00ky had no admin concept, only record tokens and root. The `user` table belongs to the app, so gating on a `role` field would both require every app to adopt one and hand users a field they could write. Instead `_00_admin` is a new internal table, root-written via `spky admin add|remove|list`, denying create/update/delete unconditionally so nobody self-promotes. Its select rule is self-scoped, so an admin confirms their own status without the roster being enumerable. Per-user assignments are derived, so writing a definition syncs nothing on its own. `fn::feature::materialize` re-evaluates one flag for all users into the already-live-synced `_00_user_feature`; `allow`/`disallow` edit the allowlist and re-materialize. Three SurrealDB behaviours shaped this, verified against surrealdb-core 3.1.5 rather than assumed: - Permission-clause subqueries run with checks disabled, so `_00_admin` can gate other tables while staying invisible itself. - DEFINE FUNCTION defaults to PERMISSIONS FULL. Omitting the clause would ship a self-service flag editor to every signed-in user, so all three mutations carry an explicit clause and a unit test asserts it. - Custom functions are not security-definer; the body runs as the caller. That is why `_00_user_feature` opens writes to admins, and why the `SELECT id FROM user` inside materialize is permission-filtered. The scheduler now stamps the root-enumerated user count so materialize throws loudly instead of silently updating only the admin. Also fixes fn::feature::hash, which was broken on SurrealDB 3: the int cast no longer accepts hex and hex literals were removed from the language, so `<int>'0x5f7bdee4'` errors. Nothing called it before (both Rust evaluators hash themselves), but every rollout rule would have failed once materialize did. Replaced with a hand-rolled hex fold that reproduces the pinned Rust golden vectors. allow/disallow are read-modify-write over the rules array, matching `spky flag`. Concurrent admins collide, but SurrealDB fails the loser with a retryable transaction conflict rather than dropping the write, so the bridge retries with jitter instead of surfacing an engine error. Tests: new Docker-backed e2e asserts the SurrealQL evaluator matches the Rust golden vectors and the full permission matrix (non-admins cannot read definitions, forge assignments, or join the roster); 10 new unit tests for overrides; the Playwright permission spec gains admin coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Feature flags could only be changed with
spky flagfrom a terminal holding root credentials. A developer running the app in a browser had no way to flip a flag for themselves, and no way to roll one out to other users without dropping to the CLI.What
A Flags tab in the DevTools extension, with two capabilities kept deliberately apart because their blast radii differ:
Remote changes cover the global
enabledbit and per-user allowlist membership. Creating, deleting and percentage rollouts stay withspky flag.The admin model
sp00ky had no admin concept, only record tokens and root. The
usertable belongs to the app, so gating on arolefield would require every app to adopt one and hand users a field they could write to promote themselves.Instead:
_00_admin, a new internal table written only by root viaspky admin add|remove|list. It deniescreate/update/deleteunconditionally, and its select rule is self-scoped — an admin can confirm their own status without the roster being enumerable.Why a materialize function was needed
_00_user_featureis already live-synced, but it is derived. Writing a definition syncs nothing on its own, and the scheduler sweep skips (user, flag) pairs that already have a row.fn::feature::materializere-evaluates one flag for all users;allow/disallowedit the allowlist and re-materialize.Three SurrealDB behaviours that shaped this
Verified against
surrealdb-core-3.1.5rather than assumed:_00_admincan gate other tables while staying invisible itself.DEFINE FUNCTIONdefaults toPERMISSIONS FULL. Omitting the clause does not make a function root-only — it ships a self-service flag editor to every signed-in user. All three mutations carry an explicit clause, and a unit test asserts it._00_user_featureopening writes to admins, and hence theSELECT id FROM userinside materialize being permission-filtered — the scheduler now stamps the root-enumerated user count so materialize throws loudly instead of silently updating only the admin.Drive-by fix
fn::feature::hashwas broken on SurrealDB 3: the int cast no longer accepts hex and hex literals were removed from the language, so<int>'0x5f7bdee4'errors. Nothing called it before (both Rust evaluators hash themselves), but every rollout rule would have failed the moment materialize did. Replaced with a hand-rolled hex fold that reproduces the pinned Rust golden vectors exactly.Concurrency
allow/disalloware read-modify-write over the rules array, matchingspky flag. Concurrent admins collide — but SurrealDB fails the loser with a retryable transaction conflict rather than dropping the write (verified with 4 parallel calls), so the bridge retries with jitter instead of surfacing a raw engine error.Tests
feature_materialize_e2e.rs,#[ignore], pinned to SurrealDB 3.x): asserts the SurrealQL evaluator matches the Rust golden vectors, and the full permission matrix — a non-admin cannot read definitions, forge a_00_user_featurerow, or join the roster.Notes for the reviewer
spky migrate/ redeploy) before the tab does anything; until thengetFlagsreturnsisAdmin: falsefor everyone and the tab says so explicitly. Targets:threads,whitepawn.migrate::testsfail on this branch, but they also fail on a cleanmain— pre-existing and unrelated.fn::job::kill/retrycomment claiming they are root-only (they default to FULL), and the scheduler sweep's skip-if-exists letting a mid-tick signup stick on a stale variant.🤖 Generated with Claude Code