Flags and rules
Force, rollout percentage, experiment assignment — first match wins, every write is a diff, a note, and a revision.
A flag is two items that Quoin keeps apart on purpose:
- Its definition: key, type, default, owner and expiry. You declare it one time in code with
defineFlags(see Quickstart). - Its rules: force, rollout percentage and experiment assignment. You edit them in Quoin, in the web app or the CLI.
Definitions ship with a deploy. Rules change without a deploy.
API, the flag screens and the rule editor built, the CLI not yet
The rule-evaluation order and the write path below are specified in docs/02-engineering.md. They are
implemented in packages/flags/src/core/evaluate.ts and edit.ts.
- Since QN-02a, the definition of a flag reaches Quoin for real, through the first-read beacon of the SDK or
through
flags.declare. The SDK reads its rules fromGET /api/flags. - As of QN-03a (
docs/reports/qn-03a.md), the one writer is real, tested code.flags.edit,flags.revertandflags.historyanswer over tRPC. The server enforces the guarded write, the required note and the revision id that "The one writer" describes. See API for the exact inputs and refusals. - As of QN-03b (
docs/reports/qn-03b.md), you can read all of it on screen. The flags list (/flags) and the flag detail (/flags/:key) show each rule in the order below. They also show the revision history with itsr-ids and notes. They show a "Why does someone see it?" box. This box runs the sameevaluate()in your browser for an email or an account id and records nothing. - As of QN-03c (
docs/reports/qn-03c.md), the writing end is also on screen./flags/newdeclares a flag./flags/:key/editedits its rules: force, rollout percentage and the condition builder, in the fixed order below. Before you save, the editor shows a preview of what was and what would be, the required note, and the revision id. If someone else saved first, the editor shows the stale-write re-plan. Every row is locked while an experiment runs. The flag detail has the Kill switch, the archive confirm with a 30-second Undo, and a Revert on every history row. - As of QN-03a-ii, the same writes also run from a terminal.
quoin loginsigns in the CLI with a key that you create at/cli/authorize. Everyquoin flagswrite shows its diff and asks before it writes. See CLI. - As of QN-08,
/rolloutsis a read-only rollout monitor. It shows every flag whose live rules have an active rollout under 100% coverage. You reach it from a "Rollout monitor →" link on the own detail page of the flag. It draws the staged ladder from real rule edits. The ladder has past and current rungs only, because a "planned" future rung has no data source yet. It also shows any guard attached to the flag and an informational lift reading for context. The lift reading is never a verdict of its own, as the report of an experiment is.
A rollout with a guard links to its own page, /rollouts/<key>. This page shows the state of the guard and a
three-bar chart (baseline, the value at the trip, the value now). After a guard pauses the flag, the page also
shows three next steps with the commands to run. To advance or pause a rollout, you still use the rule editor
above, not these screens.
How many flags a workspace holds
A Starter workspace holds up to 5 flags. Quoin counts the flags across every environment. Quoin refuses the sixth
flag where it is written. flags.declare answers plan_limit. The first-read beacon creates flags up to the cap
and skips the rest. A flag that your code declares but Quoin skipped evaluates to its code default. This is the
same value that the flag reads when Quoin is unreachable. The cap never changes the rules on the flags that you
already have. See Plans and limits.
First match wins
When Quoin decides a flag for a unit, it checks the rules of the flag in a fixed order. The first rule that matches answers the question:
- Force: an explicit override for a specific unit, or a condition that matches a set of units. You usually use it to put a specific account or QA session into a known state, without regard to a rollout or an experiment.
- Rollout: a percentage of units. Quoin hashes it by unit id, so the same unit always gets the same side of the line. If you move a rollout from 45% to 60%, Quoin only adds units to the "on" bucket. It never turns off a unit that was already on.
- Experiment: Quoin gives the unit one of the variants of the experiment. Quoin hashes the variant in the same way. Unlike a rollout, Quoin freezes the assignment for the life of the experiment. While the experiment runs, you cannot lower the weights, the variations or the coverage.
- Code default: the value declared in
defineFlags. Quoin uses it when no rule matches.
There is no reordering rule for v1. Rules evaluate newest-first. To change the effective order, remove a rule and add it again. Do not drag it up or down a list.
Kinds of flag
release(the default): a temporary flag for a feature that you ship. It always has anexpiresdate.experiment: the flag that an experiment runs behind. It also always hasexpires.ops: a permanent operational flag, for example a kill switch or a tunable knob. It has no expiry.
The one writer
Every write to the rules of a flag goes through a single writer. These writes include adding a force rule, moving a rollout percentage, starting or stopping an experiment, and turning a flag off. The writer always does these steps:
- It shows a diff of what the rule set holds now and what it would hold after the write.
- It requires a short note that explains why. The kill switch is the exception (see below).
- It produces a revision id (
r-0419). The diff, the note and the actor are permanently attached to it. - It makes the write undoable, from the CLI or the revision history in the web app:
quoin flags revert onboarding.checklist_v2 --to r-0418 --env productionThat command is the planned grammar. The procedure that it will call, trpc: flags.revert with
{ to: "r-0418" }, is real as of QN-03a. It restores the value that the named revision replaced, as a new
revision. It never rewrites history.
The one exception is the kill switch, which writes a flag straight to off. It takes no note, shows no diff and
asks for no confirmation. A production incident is not the time to write a sentence about why you turn something
off. All other writes always show the diff and always ask for the note. These writes include force rules, rollout
changes, starting or stopping an experiment, and editing segments and metrics.
Guard pauses
A guard is an automated rule. It pauses a flag when a signal that it watches crosses a threshold. An example of a
signal is an error rate. A guard pause writes off with the actor guard:<name> instead of the name of a person.
Quoin asks for a typed "resume anyway" confirmation when you resume a guard-paused flag, if both of these conditions are true:
- The last revision of the flag was written by a guard (actor
guard:<name>). - You resume at the same rollout coverage that tripped the guard.
Quoin asks for the confirmation without regard to whether the signal that tripped the guard has since cleared. When a person resumes, a trailing-window rate has usually already fallen back to baseline. So "does the condition still hold" is almost never true. The confirmation exists to catch this state: "a guard turned this off and nobody has changed anything since." The server reads this state from the guard itself, not from the latest revision. So if you roll out again at the same coverage, you do not skip the confirmation.
If you resume at a lower coverage, Quoin re-arms the guard with a fresh check counter and skips the confirmation. This is true only if nothing else reaches more people than at the pause. Quoin still asks for the phrase in these cases: someone added or widened a force rule for more than named people, or a second rollout rule has a changed coverage.
In the web app, a paused flag shows this state in its own status line: "Paused at 10% by guard:error-rate on 17
Sep. The rules are kept." The status line also offers "Resume at 10%…". This opens a confirmation that shows
exactly what changes. At the coverage that tripped the guard, the confirmation asks you to type "resume anyway".
Home lists a paused flag with the same button. A web resume records the note resume after guard:<name> (or
resume anyway after guard:<name>) for you.
If you turned a flag off yourself with the kill switch, the page offers "Turn back on…" instead. It asks for a note like any other change. The kill switch itself takes effect when you press it, with no confirmation and no note. If the server refuses, the page puts the flag back as it was and says so.
The rollout page and Home show quoin flags commands for a paused flag (quoin flags rollout, quoin flags on,
--confirm "resume anyway"). These commands run as shown from a terminal (see the CLI page). The
same writes are available in the app, on /flags/<key>.
Guards explains these items: how a guard decides to pause, the seven-day dry run before an operator turns automatic pausing on, the monitoring lines, and the runbook for a guard that fired.
Reading a flag's status outside its rules
You choose the environment for which Quoin reads the rules of a flag in exactly one place: the environment switcher
in the sidebar. Every other surface shows the environment as read-only context ("in production," --env production). These surfaces include the detail page of a flag, a CLI chip and an audit row. They do not offer
their own switcher. So there is never a question about which environment a screen describes.