Feature flags
Vaultbase ships a real feature flag system — not a glorified settings table.
Boolean / string / number / JSON flags, ordered targeting rules, percentage
rollouts with sticky bucketing, and an admin UI at /_/flags.
Data model
Section titled “Data model”CREATE TABLE vaultbase_feature_flags ( key TEXT PRIMARY KEY, description TEXT NOT NULL DEFAULT '', type TEXT NOT NULL DEFAULT 'bool', -- bool | string | number | json enabled INTEGER NOT NULL DEFAULT 1, -- master kill switch default_value TEXT NOT NULL DEFAULT 'false',-- JSON-encoded scalar variations TEXT NOT NULL DEFAULT '[]', -- JSON: named variations rules TEXT NOT NULL DEFAULT '[]', -- JSON: ordered targeting rules created_at, updated_at INTEGER);A flag’s effective value is computed by walking rules top-to-bottom and
returning the first matching rule’s variation. No match → default_value.
enabled = 0 short-circuits to default — the kill switch.
Rule shape
Section titled “Rule shape”{ "id": "9b4c1a2", "when": { "all": [ { "attr": "user.plan", "op": "in", "value": ["pro", "enterprise"] }, { "attr": "user.email", "op": "ends_with", "value": "@acme.com" } ] }, "rollout": { "value": 25, "sticky": "user.id" }, "variation": "treatment_a"}when is a boolean tree (all / any / not / leaf). Operators:
| Op | Notes |
|---|---|
eq neq | Strict equality. |
in not_in | value must be an array. |
contains starts_with ends_with | String only. |
gt gte lt lte | Number only. |
between | value: [min, max] inclusive. |
exists | Truthy attribute (ignores value). |
regex | value is a regex string. Anchors not implicit. |
Attributes are dotted paths into the evaluation context (user.plan,
request.country, feature_flags.beta_optin).
Percentage rollout
Section titled “Percentage rollout”rollout.value is 0-100. The hash of flag_key + ":" + context[rollout.sticky]
mod 100 must be < the rollout value for the rule to fire. Deterministic
— same user lands in the same bucket across processes — and sticky —
once you’re in, you stay in even if other rules change.
A losing roll causes the engine to fall through to the next rule, so a follow-up rule with no rollout can still pick up the long tail.
Server-side evaluation (hooks / routes / cron)
Section titled “Server-side evaluation (hooks / routes / cron)”// inside a hookconst enabled = await ctx.helpers.flags.isEnabled("new_checkout", { user: ctx.auth,});
const variant = await ctx.helpers.flags.getString("checkout_variant", "control", { user: ctx.auth,});
const cfg = await ctx.helpers.flags.getJson<{ brand: string }>("payment_cfg", { brand: "stripe" }, { user: ctx.auth,});ctx.auth is the verified caller — flag rules can target on
auth.id, auth.type, auth.email, auth.collection, etc.
Public bulk eval API
Section titled “Public bulk eval API”For client SDKs and external services:
POST /api/v1/flags/evaluate{ "context": { "user": { "id": "u1", "plan": "pro" } }, "keys": ["new_checkout", "checkout_variant"] // optional — omit for all} → { "data": { "new_checkout": true, "checkout_variant": "treatment_a" } }No auth required (flags are presentation-only — the targeting context the caller sends is what determines their treatment). Don’t put secrets in flag values.
Admin endpoints
Section titled “Admin endpoints”GET /api/v1/admin/flags — listGET /api/v1/admin/flags/:key — read onePOST /api/v1/admin/flags — createPATCH /api/v1/admin/flags/:key — update (partial)DELETE /api/v1/admin/flags/:key — deletePOST /api/v1/admin/flags/:key/evaluate — test-context previewAll require admin auth and emit audit-log rows under flags.* actions.
Admin UI
Section titled “Admin UI”/_/flags shows a two-pane editor:
- Left — list of flags. Color dot for enabled state, type + rule count in the meta line.
- Right — full editor: kill switch, key + type, description, default
value, variations (multivariate flags), targeting rules with conditions
- rollout sliders.
- Test context panel — paste JSON, click Evaluate, see the trace:
resolved value, variation, reason (
rule_match,disabled,no_match,missing), and which rule id matched.
Standards alignment
Section titled “Standards alignment”The eval surface mirrors the OpenFeature provider spec:
- Variations are named buckets, not anonymous values.
- Evaluation context is a freeform object, not a fixed user shape.
evaluate()returns value + reason + variation, not just the value (so callers can log telemetry on flag decisions).
A future Phase 2 will publish the engine as an OpenFeature provider so existing OpenFeature SDK code drops in without rewrites.
Limits & not-in-scope
Section titled “Limits & not-in-scope”- Single-process cache — flags refresh from DB every 5 s. A fleet of vaultbase instances all converge within that window. No WS push yet (Phase 2).
- No experimentation — flag values are reported, but conversion metrics aren’t bound to them. Phase 3.
- No prerequisites — flag A requiring flag B’s variation isn’t modeled yet. Phase 3.
- Targeting context is caller-supplied — vaultbase doesn’t infer it.
In hooks,
ctx.authis canonical; for the public eval API, the caller is responsible for passing trustworthy attributes.