# Netlify DBaaS UI → CP9 Remote Dev Full backend contract

Date: 2026-07-23

Reviewed CP9 base: `tidbcloud/controlplane9@14b1523619c4cc849843c4f5859e91c78d0dce6a`

Live reference: `staging-base-us-west-2`, inspected read-only on 2026-07-23

## Corrected answer

The requirement needs two small CP9 changes, not a new public transport or
authentication design:

1. make CP9's Kong CORS policy accept the same DBaaS UI Netlify origins that
   staging accepts; and
2. add `layers.ui.api_url` to `cp9 status`, with the same value as
   `layers.ui.url`.

Remote Dev Full already creates the required public HTTPS endpoint:

```text
https://<session>.cp9.dev.aws.tidbcloud.com
```

It terminates TLS at the session ALB and routes both the built-in UI and API
requests to Kong. A Netlify-hosted SPA can call this origin directly from the
browser.

Backend placeholders are expected in the CP9 persona and are not a blocker for
this requirement. The DBaaS UI team's existing Auth0/Netlify setup is also an
input that already works against dev/staging Kong, not a new CP9 gap.

## What staging actually does

### Kong owns console CORS through `cookie-auth`

On the live staging main host,
`us-west-2.staging.shared.aws.tidbcloud.com`, 23 normal console Ingresses use
the same plugin chain:

```text
console-plugin-cookie-filter
console-plugin-bearer-token-fallback
console-plugin-cookie-auth
console-plugin-console-header-management
```

Staging does **not** attach a separate generic Kong CORS plugin to these
routes. `cookie-auth` is the CORS owner for both cookie and bearer requests:

- `OPTIONS` bypasses authentication;
- `header_filter` runs for every response;
- an allowlisted `Origin` receives an exact
  `Access-Control-Allow-Origin`, credentials, methods, headers, exposed
  headers, and `x-cloud-cors-managed-by: api-gateway`; and
- an unlisted Origin receives no access-control allow headers.

The live staging `console-plugin-cookie-auth` allowlist includes:

```text
https://[^/]+staging-tidbcloud\.netlify\.app
https://[^/]+partner-staging\.netlify\.app
https://[^/]+-nostalgic-varahamihira-62ba08\.netlify\.app
```

The live `console-plugin-cookie-filter` allowlist includes the first two
patterns as well. This keeps cookie handling and response CORS aligned for the
ordinary DBaaS UI preview sites.

The separate staging `custom-cors` plugin is for OpenAPI/data-API route shapes,
not for the normal DBaaS UI console main-host routes.

### Live browser-protocol proof

An unauthenticated preflight from an allowed Netlify-shaped origin:

```text
Origin: https://test-staging-tidbcloud.netlify.app
OPTIONS /api/v1/auth/meta
```

returned:

```text
HTTP/2 204
access-control-allow-origin: https://test-staging-tidbcloud.netlify.app
access-control-allow-credentials: true
access-control-allow-methods: GET, POST, OPTIONS, PUT, PATCH, DELETE
vary: Origin
x-cloud-cors-managed-by: api-gateway
```

The same request from `https://evil.example` returned `HTTP/2 204` without
`Access-Control-Allow-Origin` or `Access-Control-Allow-Credentials`.

An unauthenticated real `GET` from the allowed Netlify-shaped origin returned
the expected `HTTP/2 401` and still carried the exact-origin CORS headers. This
proves staging's gateway behavior for preflight and an auth failure response,
without using or exposing a credential.

## CP9's concrete CORS mismatch

CP9 already attaches the same cookie-filter, bearer-fallback, cookie-auth, and
header-management chain to the route-parity Ingresses
([overlay](../personas/app-delivery/envs/dev-full/dbaas-ui/kustomization.yaml)).
Its cookie plugins, however, only allow:

```text
https://[^/]+\.cp9\.dev\.aws\.tidbcloud\.com
http://localhost:[0-9]+
```

([plugin configuration](../personas/app-delivery/base/api-gateway-local-premium/console-auth-plugins.yaml)).
A Netlify Origin therefore does not receive the CORS headers that staging
would return.

CP9 also has an older `console-plugin-cors` with `origins: ["*"]` attached to
three special console Ingresses
([plugin](../personas/app-delivery/base/api-gateway-local-premium/kong-plugins.yaml),
[routes](../personas/app-delivery/base/api-gateway-local-premium/console-route.yaml)).
That is not staging's current console contract and creates two policies:
three routes accept arbitrary origins, while the rest reject Netlify.

### Smallest parity fix

1. Add the staging-supported DBaaS UI Netlify patterns to both
   `console-plugin-cookie-auth.config.cors_allow_origins` and
   `console-plugin-cookie-filter.config.filt_origins`.
2. Remove `console-plugin-cors` from the three special console Ingress chains,
   then remove the unused plugin object/contract text.
3. Keep `cookie-auth` as the single gateway CORS owner across the console
   surface, matching staging.

This is a declarative persona change. It does not need a new
`allowed_frontend_origins` session field or a restored `cp9 share frontend`
command.

If production deploy-preview names are not covered by the three staging
patterns above, the frontend team should provide the exact additional Netlify
site pattern. CP9 should add that reviewed pattern to both allowlists rather
than use `"*"`.

## Authentication: no new token contract is required

The current product path is:

```text
Netlify DBaaS UI
  -> Auth0 login
  -> existing id_token
  -> Authorization: Bearer <id_token>
  -> Kong bearer-token-fallback
  -> backend/account validation
```

This is already how the frontend connects to public dev/staging Kong. In CP9,
`bearer-token-fallback` marks the request authenticated for the gateway plugin
chain, and the backend remains responsible for validating the bearer token.
`cookie-auth` still adds CORS headers during response filtering.

Therefore the earlier suggestion to treat the ID token as a temporary
exception and design a new OAuth API audience was generic OAuth advice, not a
CP9 requirement. It is withdrawn from this gap analysis. CP9 should preserve
the existing product-compatible token path.

Likewise, CP9 only needs to validate its own hosted UI's Auth0 wildcard. It
does not need to own or re-prove the DBaaS UI team's existing Netlify callback,
logout, and web-origin configuration.

## Status handoff

`cp9 status` currently reports only:

```json
{
  "layers": {
    "ui": {
      "url": "https://<session>.cp9.dev.aws.tidbcloud.com"
    }
  }
}
```

The minimal handoff change is:

```json
{
  "layers": {
    "ui": {
      "url": "https://<session>.cp9.dev.aws.tidbcloud.com",
      "api_url": "https://<session>.cp9.dev.aws.tidbcloud.com"
    }
  }
}
```

Implementation shape:

- add `APIURL string \`json:"api_url,omitempty"\`` to `StatusUILayer`
  ([schema](../internal/cluster/status.go)); and
- set it from `selected.Remote.PublicHTTPSURL` beside `URL`
  ([status builder](../internal/cmd/cluster_cmd.go)).

The two values are intentionally identical today because the built-in static
UI and all main APIs share the one Kong origin. A distinct API listener or
Netlify proxy is unnecessary.

## Acceptance

The minimal acceptance train is:

1. render the Dev Full Kong manifests and prove the Netlify patterns exist in
   both cookie plugin allowlists;
2. prove no console Ingress still references `console-plugin-cors`;
3. against a Remote Dev Full public URL, run allowed/disallowed `OPTIONS`
   probes and confirm the same exact-origin behavior as staging;
4. from the real Netlify DBaaS UI, log in with the existing Auth0 client and
   complete one representative authenticated API call;
5. prove `cp9 status --json` reports `layers.ui.api_url` equal to
   `layers.ui.url`.

No live CP9, staging, Netlify, or Auth0 mutation was performed during this
analysis.
