# Airbyte Ops Webapp

This FastMCP app hosts internal Airbyte operations workflows. The first page is the Connector Version Manager, which supports reviewing and applying connector version changes.

## Scope

Implemented workflows:

- Search and select a connector from the Airbyte Cloud registry.
- Show latest, active, and pinned version state for a selected scope.
- List connector release history for the selected connector through `airbyte-internal-ops` production database helpers.
- Enter actor, workspace, or organization scope IDs.
- Review the normalized payload in a confirmation dialog and apply set or unset override actions.
- Show parent and child scope override context when the Airbyte Config API returns scoped configuration context.

## Safety model

The default adapter uses `airbyte-internal-ops` APIs for real read paths and version override execution. `Apply change` opens a confirmation dialog showing the exact set/unset payload before calling the matching override operation.

Real override execution must require a reference URL, an override reason, and an explicit customer tier filter. The default tier shown by the app is `TIER_2`.

Mock mode preserves the same UI workflow but never loads credentials and never changes Airbyte Cloud. In mock mode, `Apply change` completes as a no-op so the workflow remains testable end-to-end.

## Auth model

The production path is intended to run as a native FastMCP app on `https://ops.internal.airbyte.ai`, with `https://internal.airbyte.ai/ops` redirecting to that canonical host. Users sign in with Keycloak Authorization Code + PKCE, and the browser sends the resulting user bearer token to the app backend for privileged Config API calls.

The default OAuth client settings match the manual Keycloak client:

- Realm issuer: `https://cloud.airbyte.com/auth/realms/airbyte`
- Client ID: `airbyte-ops-webapp-client`
- Redirect URI: `http://localhost:3000/oauth/callback` by default, or `${AIRBYTE_OPS_WEBAPP_PUBLIC_URL}/oauth/callback` when `AIRBYTE_OPS_WEBAPP_PUBLIC_URL` is set.
- PKCE challenge method: `S256`

For local development, the adapter reads these environment variables:

- `AIRBYTE_CLOUD_BEARER_TOKEN`: bearer token for privileged Config API reads.
- `AIRBYTE_CLOUD_CLIENT_ID` and `AIRBYTE_CLOUD_CLIENT_SECRET`: optional client-credentials auth for local real-mode testing.
- `AIRBYTE_CLOUD_CONFIG_API_URL`: optional Config API root override. Defaults to `https://cloud.airbyte.com/api/v1`.
- `AIRBYTE_OPS_WEBAPP_OAUTH_CLIENT_ID`: optional OAuth client ID override. Defaults to `airbyte-ops-webapp-client`.
- `AIRBYTE_OPS_WEBAPP_OAUTH_CLIENT_SECRET`: optional OAuth client secret for the server-side token exchange when the Keycloak client is confidential.
- `AIRBYTE_OPS_WEBAPP_OAUTH_ENABLED`: set to `false` to hide the OAuth path. Defaults to enabled.
- `AIRBYTE_OPS_WEBAPP_OAUTH_ISSUER`: optional Keycloak issuer override. Defaults to `https://cloud.airbyte.com/auth/realms/airbyte`.
- `AIRBYTE_OPS_WEBAPP_OAUTH_REDIRECT_URI`: optional exact redirect URI override.
- `AIRBYTE_OPS_WEBAPP_PUBLIC_URL`: public app origin used to derive `/oauth/callback` when an exact redirect URI is not set.
- `AIRBYTE_OPS_WEBAPP_MOCKONLY=1`: explicit mock mode. The UI shows a banner, loads no credentials, uses sample data from `airbyte_ops_webapp.services.connector_version_manager.demo_mode`, and makes apply a no-op.
- `AIRBYTE_OPS_WEBAPP_THEME_MODE`: optional UI color-scheme preference. Defaults to `dark`; set to `system` to follow the browser/OS preference or `light` to force light mode.

The UI also keeps an **Auth** field as a local fallback. OAuth sign-in fills it from session storage; paste a request-scoped bearer token only when testing without Keycloak.

Production database reads use the Cloud SQL Python Connector with the replica's
public IP and IAM-based auth. Cloud SQL Proxy and Tailscale are no longer
required for these queries. See Google's language connector docs:
<https://docs.cloud.google.com/sql/docs/postgres/language-connectors>.

The runtime service account must have `roles/cloudsql.client` on
`prod-ab-cloud-proj` and access to the
`CONNECTION_RETRIEVER_PG_CONNECTION_DETAILS` Secret Manager secret. These Prod
DB-side grants are documented in `infra/ops-webapp/BOOTSTRAP.md` as post-apply
steps because the service account does not exist until the Ops Webapp stack has
been applied.

## Retool source-reference notes

The user provided Retool export ZIP attachments for the Connector Version Manager and Connector Version Overview apps. Those exports were used only as source references for workflow shape and UI concepts. This app intentionally avoids checking in exported Retool files.

Key workflows reflected from the source references:

- Manager app: connector definition normalization, target scope selection, scoped configuration create/update/delete flow shape, reset behavior, active version lookup, and scope-context panels.
- Overview app: scoped configuration list, user-originated override filtering, per-connector summaries, expanded override rows, and expiration-focused concepts.

Current gaps:

- No destructive delete/update/create execution.
- No direct edit-link compatibility with Retool pages.
- Mock mode data is intentionally small and illustrative.

## Setup

For repository-wide contribution guidance, see the root [CONTRIBUTING.md](../CONTRIBUTING.md). For one-time deployment prerequisites, see [BOOTSTRAP.md](./BOOTSTRAP.md).

From this directory:

```bash
uv sync
```

## Run

Real mode:

```bash
PYTHONPATH=. PORT=3000 uv run python -m airbyte_ops_webapp.serve
```

Mock mode:

```bash
PYTHONPATH=. PORT=3000 AIRBYTE_OPS_WEBAPP_MOCKONLY=1 uv run python -m airbyte_ops_webapp.serve
```

The Ops Webapp host serves on `PORT` and proxies the MCP server on port 8000 by default. Use `python -m airbyte_ops_webapp.serve` for OAuth testing so the browser callback route is available on the same origin as the app.

Open Connector Version Manager with a default connector selected by passing FastMCP launch args:

```text
http://localhost:3000/launch?tool=manage_connector_versions&args=%7B%22query%22%3A%22destination-snowflake%22%7D
```

The launch args also accept `connector_name` or `connector` as aliases for `query`. URL fragments are not guaranteed to work for this because browser anchors are not sent to the server-side launch handler.

## Local testing plan

1. Run the static and unit checks:

   ```bash
   uv run ruff check .
   uv run pytest
   ```

2. Start mock mode and verify the no-credential path:

   ```bash
   PYTHONPATH=. PORT=3000 AIRBYTE_OPS_WEBAPP_MOCKONLY=1 uv run python -m airbyte_ops_webapp.serve
   ```

   In the UI, search `source-github`, refresh `org_example`, stage a set to `1.9.4`, and apply. The result must say `mutating: false`.

3. Start real mode with local credentials, using either client credentials:

   ```bash
   PYTHONPATH=. PORT=3000 AIRBYTE_CLOUD_CLIENT_ID="$AIRBYTE_CLOUD_CLIENT_ID" AIRBYTE_CLOUD_CLIENT_SECRET="$AIRBYTE_CLOUD_CLIENT_SECRET" uv run python -m airbyte_ops_webapp.serve
   ```

   Or a bearer token:

   ```bash
   PYTHONPATH=. PORT=3000 AIRBYTE_CLOUD_BEARER_TOKEN="$AIRBYTE_CLOUD_BEARER_TOKEN" uv run python -m airbyte_ops_webapp.serve
   ```

4. For deployed pre-Keycloak testing, leave backend credential env vars unset, paste a short-lived Airbyte Cloud bearer token into the UI **Auth** field, refresh context for a low-risk TIER_2 test scope, and stage preview first. Do not click **Apply change** unless the payload, customer tier, reference URL, and approval path are correct.

## Checks

```bash
uv run ruff check .
uv run pytest
```

These checks exercise the adapter, preview behavior, and mock apply no-op without production mutations.
