Skip to content

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.

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.

{
"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:

OpNotes
eq neqStrict equality.
in not_invalue must be an array.
contains starts_with ends_withString only.
gt gte lt lteNumber only.
betweenvalue: [min, max] inclusive.
existsTruthy attribute (ignores value).
regexvalue is a regex string. Anchors not implicit.

Attributes are dotted paths into the evaluation context (user.plan, request.country, feature_flags.beta_optin).

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 hook
const 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.

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.

GET /api/v1/admin/flags — list
GET /api/v1/admin/flags/:key — read one
POST /api/v1/admin/flags — create
PATCH /api/v1/admin/flags/:key — update (partial)
DELETE /api/v1/admin/flags/:key — delete
POST /api/v1/admin/flags/:key/evaluate — test-context preview

All require admin auth and emit audit-log rows under flags.* actions.

/_/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.

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.

  • 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.auth is canonical; for the public eval API, the caller is responsible for passing trustworthy attributes.