# pa staging-alias

Canonical `staging.<site>` subdomain pattern for the operator's
Cloudflare-Workers-hosted sites. One command flips the alias to the
branch under review.

## Pattern

```
                           +-----------------------------+
  staging.stravica.ai ---->|  stravica-ai-staging         |
  (CF Custom Domain)       |  (router worker, env=        |
                           |   STAGING_TARGET=<preview>)  |
                           +--------------+---------------+
                                          |
                                          v
              +-------------------------------------------------+
              | 350f46a0-stravica-ai.barry-ab9.workers.dev      |
              | (workers.dev preview URL — version-pinned)      |
              +-------------------------------------------------+
                                          |
                                          v
                           +-----------------------------+
                           |  stravica-ai (source worker)|
                           |  prod -> stravica.ai apex   |
                           +-----------------------------+
```

Why a router worker?

Two CF Workers limitations forced this shape:

1. **Custom Domains always serve the production deployment.** You cannot
   bind a CF Custom Domain (`staging.stravica.ai`) directly to a
   specific version of the source worker — it always serves whatever
   `wrangler deploy` last promoted to 100%.
2. **Preview URLs cannot run on a non-`workers.dev` subdomain.** CF
   explicitly states: "You cannot currently configure Preview URLs to
   run on a subdomain other than workers.dev." So a CNAME from
   `staging.stravica.ai` → `<prefix>-stravica-ai.barry-ab9.workers.dev`
   is rejected by the CF edge (cross-user CNAME).

The router-worker indirection sidesteps both. It's a 25-line proxy that
fetches `STAGING_TARGET` and returns the upstream response unchanged.
Updating the staging target = re-deploying the router with a new
`STAGING_TARGET` binding — a single API call.

## Multi-account safety drill

Cloudflare has multiple accounts in this estate (Stravica dedicated,
Clive-shared). Using the wrong account's keys would catastrophically
write to the wrong tenant. Every `set` / `unset` runs:

1. Look up the site in the registry.
2. Load the vault keys for the site's `accountKey` (`stravica` /
   `clive` / ...).
3. `GET /accounts/{account_id}` — read the account's display name.
4. Compare against `expectedAccountName` from the site registry.
5. ABORT (exit 2) on mismatch.
6. Only then write.

The drill output is echoed on stdout: `account verified: "X" (id)`.

## Usage

```bash
# Wire staging.stravica.ai to the latest preview deployment of the
# source worker. CF doesn't tag versions with branch, so this picks
# the newest version — operator must have just pushed the branch.
pa staging-alias set --site stravica.ai --branch peer-review

# Pin a specific version explicitly (precise, branch tagging not needed).
pa staging-alias set --site stravica.ai --branch peer-review \
  --version 350f46a0-c38e-48a5-8395-216d3b290dac

# Inspect current state.
pa staging-alias status --site stravica.ai

# Across all registered sites.
pa staging-alias list

# Remove the alias (binding + router worker).
pa staging-alias unset --site stravica.ai
```

## Adding a fourth site

Edit `src/staging-alias/sites.mjs`. Add one entry:

```js
'newsite.com': {
  hostname: 'newsite.com',
  sourceWorker: 'newsite',
  routerWorker: 'newsite-staging',
  accountKey: 'newsite',  // or 'stravica' / 'clive' if shared
  expectedAccountName: "Owner's Account",
  zoneName: 'newsite.com',
},
```

…and store the vault keys under the surface segment that matches how
the token is scoped: `CLOUDFLARE_WORKERS_API_TOKEN_NEWSITE` +
`CLOUDFLARE_WORKERS_ACCOUNT_ID_NEWSITE` (Workers surface), or
`CLOUDFLARE_PAGES_API_TOKEN_NEWSITE` + `CLOUDFLARE_PAGES_ACCOUNT_ID_NEWSITE`
(Pages surface). See `packages/cli/docs/gsc.md` for the candidate order.

## Exit codes

- `0` ok
- `1` usage error (bad flags, unknown site, no versions on source worker)
- `2` operational failure (account-drill mismatch, CF API write failed)
- `3` transport/IO failure (network down, non-JSON CF response)

## Tests

`node --test packages/cli/test/staging-alias/`. All CF API calls are
mocked; no real account is touched in tests.
