# Flags: changed, listed and removed

`jadpo flags` reads and changes `jadpo_flags` in the flags' store, found
as `jadpo schedules` finds its database, and runs none of the app's code
([`jadpo flags`](commands-for-background-work.md#jadpo-flags)).

## Setting a value

`jadpo flags set <flag> [<value>]` changes one part of the flag's state
and leaves the rest:

- With no option, `<value>` becomes everyone's value.
- `--users <id>,<id>` gives those users `<value>`, each `id` a `Uuid`.
- `--percent <n>` replaces the shares with one, `<value>` for `n`% of
  users; `--percent 0` removes them.
- `--split <value>=<n>,…` replaces the shares with one for each value,
  in the order written, the percentages adding up to 100 or less. It
  takes no `<value>`, and not with `--percent`.

`--users` and `--percent` go together. A `Bool` flag's `<value>` is
`true` when an option is given without one. A value is written as a
test's `flags:` writes it, `true` or a variant's name.

`jadpo flags reset <flag>` gives everyone the `default:` again, its row
deleted; with `--users`, only those users stop being named.

## The log

Each change writes a row of `jadpo_flag_changes` in the same statement
as the change: when, the flag, `set` or `reset`, its state before and
after, the name the operator's machine gives (`USER`, else `LOGNAME` or
`USERNAME`), and the database role PostgreSQL authenticated
(`session_user`), which the statement reads itself so it can't be
faked. The screen shows both, as `jadpo deliveries` does. A row is kept
for good: changes are few, and a flag's history is what a reviewer reads
when a release went wrong. Its rows, like `jadpo_flags`, hold named
users' `id`s; a user's erasure removes them from both, which comes with
erasures ([Erasures](data-requests.md#erasures)). A change is made
only to the row as it was read, so of two operators acting at once, one
acts and the other is told it changed (`flags.unavailable`).

There is no production guard and no prompt. Changing a flag is the point
of a running app's flags; a change is logged, reversible and reaches
every server within ten seconds.

## Ready to remove

A flag is ready to remove when every caller gets one value (each named
user's, each share's with a percentage above 0 and everyone's are the
same) and its row last changed seven days ago or more. A flag never
changed has no row, so it is never marked: it hasn't been released.

## Both values tested

`flag.value_untested` follows the calls each test makes, through the
actions and fixtures it reaches and the routes it sends requests
through (their `run:` and the action it runs), to every `flags.<name>`
in the bodies it reaches, whichever branch of them it takes. A test gives each flag its `flags:` value, or the
`default:`. A read the reaching tests don't give every value is an
error at the read, naming the values missing, and it can't be declined.
A test's own read of a flag isn't counted.

## Removing

`jadpo flags remove <flag> --keep <value>` refuses a project that
doesn't check, and refuses `<value>` that isn't one of the flag's. Then:

- A read that is an `if`'s whole condition, alone, behind `!`, or
  compared with `==` or `!=` to a value, decides the `if`: its block or
  its `else` stays, unwrapped to the enclosing block, and an `else if`
  chain keeps its own conditions. When the kept block ends by
  returning, what came after it in the same block can't run and goes.
- Every other read becomes the kept value, `true` or `Type.variant`.
- The declaration goes from `flags { }`. When it was the last, `flags {
  }` stays, empty, so `jadpo_flags` and its log stay: dropping them is a
  migration of its own, which comes with dropping tables.
- Each test's `flags:` line for it goes, and `flags:` with it when it
  was the last. A test whose value was another and that reached a read
  goes whole; here a read inside an `if` another flag decides is
  reached only by the tests whose values take that branch.

- An enum flag's type goes too when nothing uses it any more, and its
  file when nothing else is in it.

Imports a change leaves unused go, and each file is formatted as it
was. The result is checked, and undone with its errors when it doesn't
check. A flag a route's `run:` passes on is refused before anything
changes, since a route's `run:` passes no value written out: the
action reads the flag itself first.

## `GET /flags`

Each API with a record gets `GET <prefix>/flags` while a flag has
`client: true`. It is authenticated as the API's routes are, and
answers with a JSON object of each `client: true` flag by name, a
`Bool` as `true` or `false` and an enum as its variant's name, worked
out for the caller as an action's read is, with `cache-control:
no-store`. The OpenAPI document gives its schema, the enum's values
listed. A route the app writes at that path is `route.path_overlap`.
