# Architecture

## Resource model

seekrit is multi-tenant. Resources nest from organizations down to individual secrets:

```
organizations ─┬─ members (users, via org_memberships with a role)
               ├─ groups ──────── environments ─┬─ secrets (+ version history)
               ├─ applications ─── environments ─┤   (shared, reusable secret bags)
               │        └─ composes groups        └─ key grants (wrapped DEKs)
               ├─ service tokens (bound to one app environment)
               └─ audit log (append-only)
```

- **Organization** — a tenant. Has members, applications, groups, service tokens, and its own
  audit trail.
- **Application** — a deployable (a service, site, or worker) within an org.
- **Group** — a reusable secret bag shared across applications (e.g. `common-backend`,
  `auth-providers`). Like an application, it owns environments keyed by slug.
- **Environment** — a named context (`production`, `staging`, `dev`, …) owned by **either** an
  application or a group. Each environment owns one data key.
- **Composition** — an application environment pulls in one or more groups, matched by slug. At
  read time the layers merge, lowest precedence first: `group secrets < app secrets`.
- **Secret** — a named, encrypted value in an environment. Every write appends a new version.
- **Key grant** — a wrapped copy of an environment's data key for one principal.
- **Service token** — bound to one application environment; that binding selects the org, app, and
  environment it resolves at runtime, plus the group slices composed into it.

## Roles

Membership carries a role: `owner` > `admin` > `member`.

- **Admin and owner** manage structure (apps, environments), service tokens, key grants, and can
  read the audit trail.
- **Members** read and write secrets in environments they hold a key for.

The real access boundary is cryptographic: you can only decrypt an environment if you hold a key
grant for it. Roles gate the management API on top of that. Non-members receive `404`s for an org
so its existence can't be probed.

## Cloudflare building blocks

seekrit runs entirely on Cloudflare's edge.

| Component | Cloudflare product | Role |
| --- | --- | --- |
| API | Workers | The Hono API worker |
| Database | Postgres (PlanetScale, via Hyperdrive) | Orgs, users, secrets (ciphertext), grants, audit |
| Cache | KV | Cached identity-provider JWKS (and future session/rate-limit state) |
| Web dashboard | Workers (via OpenNext) | Next.js app serving the browser client |

The API and web dashboard run as separate Workers, backed by Hyperdrive (database) and KV
bindings.

## Request lifecycle

1. A request arrives at the API worker with a credential — a Stytch session JWT (web), a CLI
   session (a signed-in human at a terminal), or a service token (machines).
2. Auth middleware resolves the **actor** (a user — whether from a browser session or a CLI
   session — or a service token) and, for org-scoped routes, checks membership and role.
3. The handler reads or writes ciphertext in Postgres. It never handles plaintext secrets — including
   the ciphertext of a value that [references another secret](/docs/guides/references), which the
   client expands after decrypting.
4. Any mutating action writes an entry to the append-only audit log before the response returns.
   High-volume environment resolves are metered for usage/billing instead of audited per call
   (denied resolves are still audited).

## Authentication

- **Web** — Stytch B2B sign-in (Google/GitHub OAuth discovery, or email/password discovery). The
  browser holds the session; its JWT is sent as a bearer token and verified by the API against
  Stytch's JWKS (cached in KV). The auth method is opaque to the API — every method yields the same
  session JWT, so nothing server-side changes per method.
- **CLI (you)** — `seekrit login` opens a browser, you authorize the device, and the CLI saves a
  **CLI session** (`skc_…`) that authenticates as your user. The CLI generates that token itself and
  registers only its hash, so the credential never travels through the API. It carries no key
  material: decryption still unlocks your own private key with your passphrase, on your machine.
- **CLI / machines (unattended)** — service tokens (`skt_…`) sent as bearer tokens.
- **Agents** — Stytch **M2M** client credentials (OAuth client-credentials), sent as HTTP Basic
  auth or as a pre-fetched access token. Agents self-register at `/v1/signup` and authorize
  against seekrit's own client→org mapping (never token claims). A machine credential holds no
  key material, so it bootstraps a service token for anything that touches ciphertext.

The first time a valid session proves access to a Stytch organization, seekrit provisions the
matching org and membership just-in-time.
