# Branch configs

A **branch** is a disposable environment forked from a real one: `pr-142`
inherits everything from `dev`, overrides only the values that differ for that
pull request, and deletes itself when its lifetime runs out.

It exists for the config problem every preview deploy has. Without it you either
point every preview at the shared `dev` environment — so no PR can change a
value without changing it for everyone, and one leaked preview token exposes all
of `dev` — or you hand-create a real environment per PR and accumulate stale
ones nobody deletes.

## Inherit, don't copy

A branch is not a snapshot. It stores **only what you override**, and everything
else resolves from its parent at read time:

```
group secrets  <  app-env secrets  <  branch overrides  <  .env  <  process env
```

Two things follow, and both matter:

- **Creating a branch is instant, whatever the environment holds.** Nothing is
  copied or re-encrypted — a branch of an environment with 200 secrets costs the
  same as a branch of one with 2.
- **Branches track their parent.** Rotate `STRIPE_KEY` on `dev` and every open
  branch picks it up on the next read. A snapshot would have gone stale.

> **Note:** A branch can't out-reach what it forked. Reading one requires a key grant on every layer it resolves, including the parent — so branch access is always a subset of parent access, and revoking someone from `dev` ends their access to its branches too.

## Opening and closing one

```bash
# Fork dev. Lives 7 days unless you say otherwise.
seekrit branch create pr-142 --app api --from dev

# Override just what differs for this PR.
seekrit secrets set DATABASE_URL 'postgres://…pr-142…' --app api --env dev --branch pr-142

# Run against it — everything else comes from dev.
seekrit run --app api --env dev --branch pr-142 -- pnpm dev

seekrit branch list --app api
seekrit branch delete pr-142 --app api
```

`--ttl` takes `30m`, `12h`, `7d`, `2w`, or `never` (max 30 days). Expired
branches are deleted automatically, along with their overrides, key grants, and
any service token bound to them — so forgetting to clean up is safe, not a leak.

## In CI

A service token stays bound to its environment; `--branch` (or `SEEKRIT_BRANCH`)
picks a branch **of that environment**, so the token you already use for `dev`
needs no changes and no new grants.

```yaml
env:
  SEEKRIT_TOKEN: ${{ secrets.SEEKRIT_TOKEN }}   # bound to api/dev
  SEEKRIT_BRANCH: pr-${{ github.event.number }}

steps:
  - run: seekrit branch create "$SEEKRIT_BRANCH" --app api --from dev --ttl 3d
  - run: seekrit secrets set DATABASE_URL "$NEON_BRANCH_URL" --app api --env dev --branch "$SEEKRIT_BRANCH"
  - run: seekrit run -- pnpm test        # picks up SEEKRIT_BRANCH automatically
```

Then on PR close:

```yaml
  - run: seekrit branch delete "pr-${{ github.event.number }}" --app api
```

The TTL is the backstop for when that step never runs — a cancelled workflow, a
force-pushed branch, a runner that died.

`seekrit-run` takes the same `--branch` flag and `SEEKRIT_BRANCH` variable, so
containers get branch configs without the Node CLI:

```bash
seekrit-run --branch pr-142 -- ./server
```

## Who can branch

Anyone who can read the environment — creating a branch is not an admin action,
because it grants no access the creator doesn't already have. The new branch's
data key is wrapped to everyone who already holds a grant on the parent, so a
branch is usable by the same people the moment it exists, with no grant
paperwork. Pass `--no-share` to keep a branch to yourself.

> **Note:** That fan-out happens once, at creation. A principal added to the parent *after* a branch exists — a service token minted later, a teammate granted later — won't hold that branch's key and gets `no key grant for branch …`. Grant it explicitly, or just re-create the branch; they're cheap and short-lived. Branches created after the principal are unaffected.

Branches are one level deep (you can't branch a branch) and live under
application environments, not groups. Their names share the application's
environment namespace, so a branch can never collide with a real environment.

## Seeing where a value came from

`--explain` marks inherited values against their source layer and overridden
ones against the branch:

```bash
seekrit run --app api --env dev --branch pr-142 --explain -- pnpm dev
```

```
DATABASE_URL      branch:api#pr-142
STRIPE_KEY        app:api/dev
SHARED_CA         group:common@dev
```
