# Feature flags

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. You declare every
flag once, in the file `flags.jadpo` at the root of the project, with its
type, its default, its owner and the date it expires. The `///` comment
above a flag is its description, as for every declaration.

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

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

A flag is a `Bool`, or an enum when it has more than two variants. Code
reads it as `flags.new_checkout`, in actions and in a route's `run:`
arguments. Functions and queries can't read a flag, 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.

```jadpo
// domains/ordering/actions/place_order.jadpo
action place_order(user_id: User.id, input: PlaceOrder) -> OrderView {
    if (flags.new_checkout) {
        return place_order_in_one_step(user_id, input)
    }
    ...
}
```

## Changing a flag

A flag's current state lives in your app's own database, in a table Jadpo
owns. You change it while the app runs, with `jadpo flags` against an
environment
([Commands for services, calls, email and stored data](commands-for-services-calls-email-and-stored-data.md)).
Each change is logged with who made it.

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 --env production --users 0192... --percent 10
jadpo flags set checkout_layout --env production --split classic=50,compact=50
jadpo flags list --env production
```

## 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](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
([Background work](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, 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](test-settings.md)). `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, 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".

## 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.

## 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.

## 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`](settings-config-jadpo.md)).
