# Feature flags

**Partly built.** Missing: turning a flag on for a named tenant; erasing a person from the flag tables. See [What is built](/docs/built-in-services-built.md).

[Ship a change behind a flag](/guides/guide-ship-a-change-behind-a-flag.md) does the whole job, start to finish.

A feature flag lets you ship code switched off, then turn it on for some
users, a share of users, or everyone, without a deploy. Its state is in your
app's PostgreSQL database, in a table Jadpo owns. Each server holds a copy in
memory and rereads it at most every five seconds, so a change applies within
ten seconds and reading a flag runs no query. A percentage rollout is a hash of
the flag's name and the user's `id`, so the same user always gets the same
answer.

A flag is temporary. You add it with an `expires:` date, roll the change out,
and remove the flag once everyone has one value; `jadpo check` fails once a
flag is past its date, so none stays by accident. The dates here are far ahead
only so these examples never expire. In your app, pick one a few weeks or
months off.

You declare every flag once, with its type, its default, its owner and the date
it expires. `flags.jadpo`, at the root of the project:

```jadpo
flags {
    /// The one-page checkout replacing the three-step form.
    new_checkout: Bool {
        default: false
        owner: "payments"
        expires: "2099-01-31"
    }
}
```

`flags { }`, `Bool` and its settings are built into Jadpo. `new_checkout` is
your name for the flag and `"payments"` whoever answers for it. The `///`
comment above a flag is its description, as for every declaration. `app.jadpo`
needs nothing for it: a project's one database holds the flags.

An action reads it as `flags.new_checkout`. `CheckoutSteps` is a type of your
app, since an action's result is always a named type.

`domains/ordering/types/checkout_steps.jadpo`:

```jadpo
/// How many pages the checkout takes.
type CheckoutSteps = Int {
    min: 1
    max: 3
}
```

`domains/ordering/actions/checkout_page_count.jadpo`:

```jadpo
use ordering/types { CheckoutSteps }

/// How many pages this customer's checkout takes.
action checkout_page_count() -> CheckoutSteps {
    if (flags.new_checkout) {
        return 1
    }
    return 3
}
```

Code reads a flag in actions and in a route's `run:` arguments. Functions and
queries can't read one (`flag.read_context`), so you pass them its value as a
parameter. As with `clock.now`, the value is fixed for the whole operation, so
one request never sees a flag change halfway through.

Every read needs a test for each value, and a test chooses the value with
`flags:`.

`domains/ordering/actions/checkout_page_count.test.jadpo`:

```jadpo
test "the three-step form is shown by default" {
    assert checkout_page_count() == 3
}

test "the new checkout takes one page" {
    flags: {
        new_checkout: true
    }
    assert checkout_page_count() == 1
}
```

On a first run, `jadpo migrate new initial` writes the migration and `jadpo
records update` the records, as `jadpo check`'s `next:` lines say. Commit what
they write.

## A flag with named values

A flag is a `Bool`, or an enum whose variants carry no data. An enum names its
values, so it suits comparing two layouts as well as choosing among more.

`domains/ordering/types/checkout_layout.jadpo`, a type of your app:

```jadpo
/// How the checkout's form is laid out.
type CheckoutLayout = Enum {
    classic
    single_column
}
```

`flags.jadpo`, with `checkout_layout` and its `use` line new:

```jadpo
use ordering/types { CheckoutLayout }

flags {
    /// The one-page checkout replacing the three-step form.
    new_checkout: Bool {
        default: false
        owner: "payments"
        expires: "2099-01-31"
    }

    /// Which layout the checkout uses while we compare two.
    checkout_layout: CheckoutLayout {
        default: CheckoutLayout.classic
        owner: "payments"
        expires: "2099-12-15"
    }
}
```

`domains/ordering/actions/layout_shown.jadpo`:

```jadpo
use ordering/types { CheckoutLayout }

/// The layout this customer's checkout uses.
action layout_shown() -> CheckoutLayout {
    return flags.checkout_layout
}
```

`domains/ordering/actions/layout_shown.test.jadpo`, a test for each variant:

```jadpo
use ordering/types { CheckoutLayout }

test "the classic layout is shown by default" {
    assert layout_shown() == CheckoutLayout.classic
}

test "a test can give the single-column layout" {
    flags: {
        checkout_layout: CheckoutLayout.single_column
    }
    assert layout_shown() == CheckoutLayout.single_column
}
```

## Changing a flag

A flag's current state lives in your app's own database, in a table Jadpo
owns, `jadpo_flags`, in the database `application { flags: { store: } }`
names, or the only one there is. With several databases and none named,
the flags are `flag.store_required`. You change it while the app runs,
with `jadpo flags` against that database
([`jadpo flags`](/docs/commands-for-services-calls-email-and-stored-data.md#jadpo-flags)). Each change is logged with
who made it: the name the operator's machine gives and the database role
that made it. Whoever may write to the database may change a flag, as
for every operator command; Jadpo has no operator accounts of its own.

Each server holds the flag values in its own memory and refreshes them
every few seconds, so a change applies within ten seconds. Flags are
never held in the cache. If the database can't be read, each server
keeps the last values it had, or the defaults, so reading a flag never
fails a request.

Flags aren't a capability, and no outside flag service fills them.

```text
jadpo flags set new_checkout --users 0192f1c4-7a3e-7c1d-9b2a-5e6f7a8b9c0d --percent 10
jadpo flags set checkout_layout --split classic=50,single_column=50
jadpo flags list
```

The database is found as `jadpo schedules` finds its own: the one
`--url` names, else the address its connection's setting gives, else a
running `jadpo dev`'s. There is no `--env` flag: an environment is its
configuration, so production's flags are changed with production's
address.

## Who gets which value

The first rule that matches wins:

1. Named users get a fixed value.
2. Named tenants get a fixed value. This rule comes once tenancy is
   settled for flags ([Tenancy](/docs/tenancy.md)).
3. A percentage of users, chosen by a stable hash of the flag's name and
   the user's `id`. The same user always gets the same answer, raising 10%
   to 20% keeps the first 10% in, and each flag picks a different 10%.
4. Everyone else gets the flag's current value, at first its `default`.

The runtime knows the caller, as it does for policy, so you don't pass
it. Schedules and subscribers have no caller
([Policy and background work](/docs/policy-and-background-work.md)), so they get the
everyone value. An action that needs a user's variant later puts the
value in the event it emits.

## What the compiler checks

- `flags.new_chekout` is a compile error, `flag.unknown`, like any
  unknown name.
- Every flag read needs a test for each value. A test chooses the value
  with `flags: { new_checkout: true }`, beside `clock:` and `config:`
  ([Test settings](/docs/test-settings.md)), for every caller in
  the test; a flag it doesn't name has its `default:`. `jadpo check`
  fails until the tests that reach each read cover both `true` and
  `false`, or every variant.
- A flag past its `expires:` date is an error naming its owner,
  `flag.expired`, and you can't decline it. `jadpo check` judges the date against today, and
  `jadpo build` against the commit's date, so rebuilding a merged
  revision is never blocked by a date passing. To keep a flag longer you
  edit the date, so keeping one is a reviewed choice.
- Whether a flag has sat at 100% or 0% is runtime state the compiler
  can't see, so `jadpo flags list` marks those flags "ready to remove":
  every caller has had one value for seven days.

## Removing a flag

`jadpo flags remove new_checkout --keep true` rewrites your source: each
read becomes `true`, the branch that can no longer run goes, the
declaration goes, and test overrides for the removed value are deleted.
Jadpo can do this exactly because it knows every read.

A test that gave the flag a removed value, or left it at a `default:`
that is removed, and reached a read, tested the branch that has gone, so
it goes too; a test that gave it the kept value loses only that line. The
command lists every change file by file, and a removal whose result
wouldn't check is undone, so the project always checks after it. The
flag's row in the database is left: a row no flag has is never read.
`flags { }` stays when its last flag goes, with the tables.

## Flags in browsers and apps

A flag with `client: true` is also returned, worked out for the caller,
by a generated `GET /flags` route in the OpenAPI document. Each API gets
one under its prefix, answered for whoever the request signed in as, so
a page reads the values its user gets. A flag without `client: true`
never leaves the server.

```json
{ "new_checkout": true, "checkout_layout": "single_column" }
```

## What flags are not for

Flags are for releasing code. What a customer's plan includes is your
app's data, guarded by policy. A setting that differs between
environments is configuration
([Settings: `config.jadpo`](/docs/settings-config-jadpo.md)).

## Details

The exact rules, for when you hit an edge case.

| Page | What it covers |
|---|---|
| [Flag rules: declaring, reading, testing](/docs/flags-declared-read-and-tested.md) | How a flag is declared, how your code reads it, and how a test sets it. |
| [Flag rules: changing, listing, removing](/docs/flags-changed-listed-and-removed.md) | How `jadpo flags` changes and lists a flag's value, and how a flag is removed from the code. |
