> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploybaseline.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> How a request travels through Baseline: registry, manifests, plugins, store, webhooks, composed services, and the shared UI.

This page follows one request from the `baseline start` command to a provider route handler, and names the file that owns each step. Each built-in service runs in its own provider process, so the path splits into the host side (the CLI) and the provider side (core's `runProvider`). For the user-facing view of the same system, see [Architecture](/architecture.md) in the Reference section.

## Request flow

```mermaid theme={null}
flowchart TD
  A["baseline start --service stripe"] --> B["packages/cli/src/registry.ts<br/>port slots + catalog.generated.json"]
  B --> S["packages/cli/src/supervisor/<br/>fork the provider entry, hello, start, listen"]
  S --> R["core runProvider(manifest)<br/>in the provider process"]
  R --> C["manifest.load()<br/>dynamic import of the provider"]
  C --> D["LoadedService<br/>plugin, seedFromConfig, prepareSeed"]
  D --> E["core createServer(plugin, options)"]
  E --> F["Middleware: fonts, error handlers, CORS,<br/>auth (tokens + fallback identity), rate limit headers"]
  F --> G["plugin.register(app, store, webhooks, baseUrl, tokenMap)"]
  G --> H["Route handlers"]
  H --> I[("Store<br/>collections + setData")]
  H --> J["WebhookDispatcher<br/>signed deliveries, retries"]
  K["plugin.seed() then seedFromConfig()"] --> I
  I --> L["Saved state<br/>store + tokens + webhook subscriptions"]
```

## Host and provider processes

The host and each provider speak the protocol in `core/src/protocol.ts` over Node IPC:

* The provider says `hello` (protocol version, name, version, `stateVersion`, fault kinds). The host checks the version and answers `welcome`.
* `start` carries the service's seed section, tokens, identities, faults, saved state or generated secrets, and the port. The provider merges identities with its `mapIdentity`, runs `prepareSeed`, builds the app with `createServer`, restores or seeds, and answers `ready` with its URL and secrets. `listen` binds the port.
* `snapshot`, `restore`, `reset`, `faults`, and `stop` do what they say. The provider sends `dirty` after a write, so the host saves only providers that changed.
* A request to `/_baseline/control`, `/_baseline/faults`, or `/_baseline/state` on a provider's port is sent to the host as `control` and answered there, with the host's token and host checks.

`core/src/run-provider.ts` is the provider side, and every provider's `src/provider.ts` is one call to it. `core/src/provider-client.ts` is the host side of one process. `packages/cli/src/supervisor/` starts providers in parallel, answers their control requests, restarts a crashed one from the last state it holds (three times a minute at most), and stops them. The CLI forks the resolved package's `dist/provider.js`: from the project, the cache, or, in this repository, the workspace package (`supervisor/provider-source.ts`). The CLI's tarball holds no provider code; `scripts/check-bundle.mjs` reads the build's metafile to prove it, and `scripts/slim-cli-acceptance.mjs` installs the packed CLI and pulls the defaults from a local registry. A provider's own tests boot its plugin in process with `@deploybaseline/testing`.

## The CLI registry

`packages/cli/src/registry.ts` holds `SLOTS`, every built-in provider by name with an explicit port slot, and reads the rest of what the CLI knows about each provider from `catalog.generated.json`. The main CLI process never imports provider code: `list`, `--help`, `init`, config validation, and `/docs` work from that data alone, and `packages/cli/src/__tests__/bundle.test.ts` fails if the binary or the programmatic API comes to import a provider. Provider code runs only in a provider process.

* With no config and no `--service`, each default service listens on the base port plus its slot (`serviceSlot`), so GitHub is on 4001 and Twilio on 4012.
* A released slot never changes and is never reused. A new provider takes the next unused number (`pnpm new:provider` picks it), and `packages/cli/src/__tests__/registry.test.ts` pins the released slots.
* `DEFAULT_SERVICES` is the set that starts with no config (the 0.14.0 providers). Providers added later start only when named.

## Package data

`pnpm provider-metadata` (`scripts/provider-metadata.mjs`) reads every manifest from source and writes:

* a `baseline` field in the provider's `package.json`: `protocol`, `entry` (`./dist/provider.js`), `name`, `seedKey`, `aliases`, `stateVersion`, `label`, `endpoints`, `identities`, `init`, and `apiReference`
* `init.json`, the starter config `init` writes
* `packages/cli/src/catalog.generated.json`, the same data for every built-in provider

CI runs it with `--check`. The host reads a provider package through `packages/cli/src/providers/package.ts`, which checks the `baseline` field and refuses a package that speaks another protocol. A provider's `dist/provider.js` inlines core and every runtime dependency (`jose`, `graphql`, `ws`), so it starts from an unpacked tarball with nothing installed next to it; `provider-package.test.ts` packs Stripe and GitHub and checks that.

## Provider packages that are not built in

A `services.<name>.provider: npm:<package>@<range>` entry (`packages/cli/src/providers/reference.ts`) runs a package through the same resolver and supervisor, with no workspace fallback and no port slot. `resolveProjectProviders` in `packages/cli/src/providers/project.ts` refuses one whose exact version and integrity `baseline.lock` does not mark `trusted: true`, unless a `confirm` callback says yes; a yes pins it before it runs. `start` passes a terminal prompt (`providers/trust-prompt.ts`) when stdin and stdout are a terminal and `CI` is unset, and a watch worker sends the question to the CLI process over IPC. `trust` grants it without a prompt. `examples/provider-template` is the package a third party starts from, and `scripts/third-party-acceptance.mjs` packs it, serves it from a local registry (`scripts/lib/local-registry.mjs`), and runs it next to Stripe in CI.

## Manifest versus `load()`

Each provider exports a `ProviderManifest` from `src/manifest.ts` (published as `@deploybaseline/<name>/manifest`). The interface lives in `packages/@deploybaseline/core/src/manifest.ts`. The package data above is generated from it, so the manifest stays the one place to edit.

| Field | Purpose |
| - | - |
| `name`, `seedKey`, `aliases` | Service name and its top-level config key |
| `label`, `endpoints` | Text shown by `list` and `--help` |
| `stateVersion` | Version of the saved state shape; bump it when older saved state can no longer load |
| `identities` | True when `load()` returns a `mapIdentity`, so the provider seeds shared `identities:` as users |
| `defaultFallback()` | Identity used when a request carries an unknown token. Code, not package data: several providers read the seed |
| `initConfig` | Starter config written by `init` |
| `load()` | Dynamic `import("./index.js")` returning the plugin and its seed hooks |

The manifest must stay cheap: importing it must not load the provider. That is what keeps `--help` and `list` fast, and `scripts/check-bundle.mjs` fails CI when `--help` loads a provider chunk or more code than the budget in `packages/cli/bundle-budget.json`.

## `ServicePlugin.register`

`load()` resolves to a `LoadedService` whose `plugin` implements `ServicePlugin` (`core/src/plugin.ts`):

```ts theme={null}
interface ServicePlugin {
  name: string;
  register(app: Hono<AppEnv>, store: Store, webhooks: WebhookDispatcher, baseUrl: string, tokenMap?: TokenMap): void;
  seed?(store: Store, baseUrl: string): void;
}
```

`createServer` in `core/src/server.ts` builds one app per service: it mounts font routes, the error handlers, CORS, the auth middleware, and rate limit headers, then calls `plugin.register`, then installs a JSON 404. The `app` is core's HTTP layer (`core/src/http.ts`), which follows Hono's API and also records route hits and response samples for the test gates when the matching environment variables are set.

Providers usually split routes into `src/routes/*.ts` functions that take a `RouteContext` (`{ app, store, webhooks, baseUrl, tokenMap }`). Seeding runs `plugin.seed()` first and then `seedFromConfig(store, baseUrl, config, webhooks)` with the provider's section of the config file.

## Auth and fallback identities

The auth middleware (`core/src/middleware/auth.ts`) reads `Authorization: Bearer <token>` or `token <token>`:

1. A known token from the token map sets `authUser`, `authToken`, and `authScopes` on the context.
2. An unknown, non-empty token falls back to the provider's `defaultFallback()` identity (for Resend, `re_test_admin`). This is why any token works out of the box.
3. A JWT that starts with `eyJ` is checked with the service's `AppKeyResolver` when it has one (GitHub Apps).

Providers that need stricter rules (Resend API key permissions, for example) add their own middleware in `register`.

## Store and saved state

`Store` (`core/src/store.ts`) holds typed collections with indexes (`store.collection<T>("resend.emails", ["uuid"])`) plus a key value area (`store.setData`, `store.getData`) for maps and flags.

**All provider state lives in the store.** Saved state is a snapshot of the store, the token map, and the webhook subscriptions (`ServiceState` in `core/src/state.ts`). Anything kept in a module variable or a closure inside `register` is lost on restart. When saved state was written with a different `stateVersion`, Baseline refuses it with a clear message instead of serving broken data.

Persistence backends implement `PersistenceAdapter` (`core/src/persistence.ts`): a JSON file by default, or `@deploybaseline/state-sqlite` and `@deploybaseline/state-libsql`.

## Webhooks

`WebhookDispatcher` (`core/src/webhooks.ts`) keeps subscriptions, delivers events, and logs every delivery. With a `retry` policy it retries network errors, 429, and 5xx in the background with exponential backoff. `renderWebhookDeliveries` in `ui.ts` draws the delivery log for an inspector tab.

Sign outbound payloads with the helpers in `core/src/webhook-signing.ts`, never with hand-built HMAC headers. Each has a `verify*` partner for tests:

`svixSignature`, `githubSignature256` (also Meta's `X-Hub-Signature-256`), `hmacSha256Hex`, `hmacSha256Base64`, `ed25519Signature`.

A scheme only one provider uses lives in that provider: `stripeSignature` in `stripe/src/signing.ts`, `slackSignature` in `slack/src/signing.ts`. Each provider bundles its own copy of core, so core carries what more than one provider shares.

## Composed services

A provider with several APIs behind one port and one seed key (Google: Auth, Gmail, Calendar, Drive, Sheets, Docs) is built with `composeService` from `core/src/compose.ts`:

* Each `ServiceModule` registers its own routes and declares the collections it owns.
* Collection names must be namespaced (`gmail.messages`). Two modules may not own the same collection or register the same route, and `composeService` throws at startup if they do.
* Modules register in array order, so a module with catch-all routes goes last.
* `moduleRoutes()` reports which module registered which route, which the coverage gate uses for per-module matrices.

When splitting an existing provider into modules, keep the existing collection names: saved state refers to them.

## Shared UI

Every emulator page (inspectors, OAuth sign-in and consent, checkout, inboxes) is rendered with `packages/@deploybaseline/core/src/ui.ts`, which supplies the shared head (Geist fonts, favicon, CSS), the brand bar, and the footer.

| Page type | Function |
| - | - |
| Centered card (OAuth sign-in, email detail, checkout) | `renderCardPage` |
| Error state | `renderErrorPage` |
| Sidebar plus content (OAuth app settings, Slack inspector) | `renderSettingsPage` |
| Tabbed data dashboard | `renderInspectorPage` |
| OAuth `form_post` auto-submit | `renderFormPostPage` |
| User picker in OAuth flows | `renderUserButton` |
| Consent permissions and decline link | `renderConsentScopes`, `renderCardCancel` |
| Collapsible JSON preview | `renderJsonDetails` |

Use the existing classes (`.inspector-table`, `.s-card`, `.org-row`, `.badge`, `.empty`) instead of inline styles. If a new page type does not fit, add the styles and a render function to `ui.ts` so every emulator can reuse them.

## GraphQL and WebSockets

* GraphQL providers serve their endpoint with `graphqlEndpoint` and build the schema with `buildSchema` from `@deploybaseline/graphql`. Root fields (`Query.issues`) are recorded alongside routes, so coverage matrices list them too.
* WebSocket endpoints are registered with `app.upgrade` and appear as `WS /path` routes. The `@deploybaseline/ws` kit provides the handshake, connection registry, heartbeat, and resume buffer.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.