A read scope is the partition for a flag value and its evidence.
A flag selects behavior through a declared ladder. Each climb needs evidence from the current rung and its exact read scope.
Read Flags climb on evidence and Feature flags first.
1. Decide whether a flag fits
Use deployment Config for kill switches and safety stops. KV is eventually consistent. Another location can retain an old value for 60 seconds or more.
off records a stop without waiting for approval. It does not make KV propagation immediate. A stop that must affect every request immediately needs a stronger control path.
EVENTS_ENABLED stays deployment Config because it selects cloud resources. Change its Config value and review the infrastructure plan.
2. Declare the ladder
Use defineFlag in core or the child's declaration module.
Declare:
- A stable name and JSON-encoded value schema.
- The safe
default,owner,purpose, and literalremoveBydate. - A nonempty
rungstuple. Put the default value first. - A unique name and value for each rung.
- An
authority:agent,arbiter, orowner. - An evidence exit for every non-final rung. End with one final rung.
- A child-supplied
readScopeschema. Omission means the literalglobalscope. - A
killpolicy: default rung, previous rung, or an explicit refusal reason. - A
failurePolicy. The default isuse-catalog-default; userefusewhen a fallback would be unsafe.
Use FlagExit, FlagThreshold, FlagKill, and FlagTarget constructors from @rat-stack/core/flag-control.
The declaration rejects duplicate rung names or values. It also rejects a misplaced final exit or a first value different from the default.
3. Name each exit's evidence
For every exit measurement, name:
- The measured
event. - Its
readlocation, such as an acceptance report or a named query. - Its threshold:
at-leastorat-most, with a finite count. - The read scope where that measurement applies.
Record the measured value and a source ref through record_flag_evidence. The event, read location, and current rung must match the declaration. A newer observation replaces that event's current measurement. The attempt ledger retains submitted observations. A flip clears current evidence. An old measurement cannot qualify a later visit to that rung.
A schema mismatch returns FlagScopeMismatch with a repair field. Readers, evidence writers, and flips enforce the same schema. Evidence from one scope never qualifies another scope.
4. Compose authority and storage
Mount flip_feature_flag, record_flag_evidence, and read_flag_ledger only on the child's operator surface. Public readers and the sandbox remain read-only.
Authenticate operator requests in the child's composition before dispatch. Keep token authentication outside Approval, because off bypasses approval.
Provide the capability Approval service at composition. It authorizes the requested role, target, scope, and quoted approvalRef. A role field or token name does not grant authority. Climbs above agent need a nonempty approval quote as well as approval.
flip_feature_flag calls implement's approval gate for climbs. For off, its audited handler supplies an always-granted approval implementation. Test this path with a service that denies every other call.
Use cloudflareFlagControlLayer() from @rat-stack/flags/cloudflare for the default ledger. For a child catalog, pass the definition returned by defineFlagLedger(registry, deployOnly). Each flag and scope routes to its own Durable Object. The normal cloudflareFlagsLayer reads KV and never calls a Durable Object.
A projection is a copy derived from a committed value.
The DO commits the authoritative value, receipt, and dirty projection marker in one storage transaction. An alarm projects committed values to KV. Failed projections retain their markers. Cloudflare retries a failed alarm six times, starting with two seconds of backoff. After the final failed retry, the adapter schedules a fresh window at the next exponential backoff step. The operator can also call the internal redrive method after recovery. A successful flip means committed, not globally visible.
kvFlagControlLayer(registry, kv) refuses authoritative flips and evidence writes with FlagAtomicityRequired by default. Reads and attempt journals remain available. A composition can accept the weaker guarantee with the typed option { authoritativeWrites: "accept-non-atomic-unserialized" }. Pass it as the third argument. This permits non-atomic writes without cross-isolate serialization; it does not make them safe for competing writers. The memory adapter supplies the same domain behavior for tests.
5. Prove and remove
- Run generated flip and evidence histories across scopes.
- Prove no climb without local evidence, no elevated climb without approval, down only through
off, and every attempt recorded. - Plant one violation of each rule. Record the failing property and replay information. Restore the code.
- Crash after commit and before projection. Recreate the ledger, redrive, and prove the replica converges.
- Check readers during that gap. They may see an older value or the declared default.
- Keep the lifecycle model green.
- Run
pnpm turbo run check test build. - Remove the flag and its call sites by
removeBy, or make its final behavior permanent.
Report the flag's purpose, owner, removal date, scopes, exits, approval boundary, selected adapter, and proof. Name provider behavior that local tests do not establish.