---
id: "8.10"
title: "Protected connections"
dept: ops-and-infrastructure
status: draft
owner: Param
branded: false
last_verified: "2026-10-26"
sources:
  - "brew origin/main d6154b3: db/brand-wipe.js:8-32, 235, db/delete-brand.mjs:10, test/brand-wipe-pg.test.js:61-79, connectors/protected.js:1-4, connectors/index.js:70, 148, 269, 429, vault.js:27, tools/prod-env.sh:9, db/connector-ping.mjs:1-24, db/status.mjs:1-15, db/reset-jams.mjs:7, 18-19, db/partner-copy.js:6-8, test/qv1-roles-security.test.js"
  - "brew/.wt-prod/docs/production.md:21, 50"
  - "memory never-disconnect-arata-meta, brew-shopify-connector-qa, brew-prod-backend, brew-live-stores, brew-wipe-data, brew-meta-connector-qa (index line)"
---

# Protected connections

## Answer

Arata's Meta and Shopify connections are never disconnected, reconnected, re-selected or revoked, on either site, and the code refuses those actions for any brand listed in `PROTECTED_CONNECTIONS`; this page says what the guard covers, what it does not, and how to check the connections are still alive.

## Today

### The rule

- Never disconnect, reconnect or re-select Arata's Meta connector, in any case. Param, 22 Oct. [src: memory never-disconnect-arata-meta]
- Never disconnect Arata's Shopify. [src: memory brew-shopify-connector-qa]
- Never revoke Meta grants. [src: memory brew-meta-connector-qa]
- Arata stays connected on both staging and production: the vault was copied, never reconnected. [src: memory brew-prod-backend] [src: brew/.wt-prod/docs/production.md:21]
- Disconnect and reconnect tests run only on Cozy Snugglet. [src: memory never-disconnect-arata-meta] [src: memory brew-live-stores]

### What Arata has connected

| Account | How | Why it can be shared by both sites | Source |
|---|---|---|---|
| Meta Ads, ad account act_2942894532542334 | OAuth on the SquareUp Meta app, connected 22 Oct | Meta token never refreshes | [src: memory never-disconnect-arata-meta] [src: brew/db/partner-copy.js:6-8] |
| Shopify | Arata's own app, route `own-oauth`, offline token | Token never expires or refreshes | [src: memory brew-shopify-connector-qa] [src: brew/db/partner-copy.js:6-8] |

If Arata's Shopify token ever starts rotating, production becomes the only refresher and staging uses a token broker. [src: memory brew-prod-backend]

### The guard in code

| Layer | What it refuses | Source |
|---|---|---|
| `PROTECTED_CONNECTIONS` | List of protected brands; default `arata-in` when unset; production sets it to `arata-in` | [src: brew/connectors/protected.js:1-4] [src: brew/tools/prod-env.sh:9] |
| Connector routes | `disconnect`, `select`, `key`, `connect` on an existing protected connection, unless a root admin sends `force: true` | [src: brew/connectors/index.js:429] |
| Replacing a connection | Refused for a protected brand unless forced by a root admin | [src: brew/connectors/index.js:148] |
| Disconnect function | Refused for a protected brand unless forced | [src: brew/connectors/index.js:269] |
| Vault | A protected brand's stored sign-in is never replaced, except a refresh or a force | [src: brew/vault.js:27] |
| Jam reset | Refuses to write connections, vault or live paths | [src: brew/db/reset-jams.mjs:7] [src: brew/db/reset-jams.mjs:18-19] |
| Brand reset, `db/reset-brand.mjs` | Keeps connections, vault, live numbers and the connected ledger, and fails the run if they changed by a byte | [src: brew/db/brand-wipe.js:15-17, 235] |
| Data request deletion, `db/delete-brand.mjs` | Refuses any brand in `PROTECTED_CONNECTIONS`; take it out of the setting first, deliberately | [src: brew/db/brand-wipe.js:27; brew/db/delete-brand.mjs:10] |
| Both brand tools | Refuse the five design partners unless `--partner-override --confirm-slug <slug>` | [src: brew/db/brand-wipe.js:28-30; brew/test/brand-wipe-pg.test.js:61] |
| Shopify webhooks | Uninstall and redact disconnect only `app:'brew'` connections, so Arata's own app is untouched | [src: memory brew-shopify-connector-qa] |
| Tests | QV round 1 found the guard could be bypassed and fixed it with a test | [src: memory brew-prod-backend] [src: brew/test/qv1-roles-security.test.js] |

### What the guard does not cover

- Provider-side grants: revoking the app at Meta or Shopify is outside our code. [src: memory brew-wipe-data]
- The bucket scripts `tools/reset-brand.sh` and `tools/flush-live.sh` still have no protected check, but since 26 Oct they exit 3 unless `STORAGE=gcs`, so they cannot run against what the sites read. [src: brew/tools/reset-brand.sh:11-19; brew/tools/flush-live.sh:17-26]
- A deletion never revokes a platform grant; it prints the grants the brand revokes itself. [src: brew/db/brand-wipe.js:7, 240]
- The `force` path is open to root admins. [src: brew/connectors/index.js:429]

## Tomorrow

| Item | Version | Tag |
|---|---|---|
| Arata protected on both sites | v1 | Stays |
| Every design partner protected once its accounts are connected | v1 | Changes, see Open |
| Wipe tools that refuse protected brands in code, on origin/main since 26 Oct | v1 | Stays |
| A daily ping of protected sign-ins, alerting when one stops reading | v1 | New |
| Token broker for rotating tokens shared by two sites | v2 | New, only if a token starts rotating |

## How we do it

1. Before any script that touches `private/vault/`, `connections.json` or a disconnect, check it skips every protected brand.
2. Check the connections still read, read only, no token printed:

```bash
gcloud run jobs execute brew-prod-ping --region=asia-south1 --wait
gcloud run jobs execute brew-staging-ping --region=asia-south1 --wait
gcloud run jobs execute brew-ops --region=asia-south1 --wait
```

The ping jobs run `db/connector-ping.mjs arata-in`; `brew-ops` runs `db/status.mjs`, which lists every protected brand's connection states. [src: brew/db/connector-ping.mjs:1-4] [src: brew/db/status.mjs:1-2] [src: memory brew-prod-backend]
3. After a database restore, run the production ping. Never reconnect Arata to fix a failed ping; tell Param. [src: brew/.wt-prod/docs/production.md:50]
4. To protect another brand, add its slug to `PROTECTED_CONNECTIONS` in `tools/prod-env.sh` and `deploy.sh`, then deploy and promote.

## Numbers

| Metric | Definition | Where measured |
|---|---|---|
| Protected sign-ins reading | Protected accounts whose ping succeeds, out of all | `db/connector-ping.mjs`; Proposed as a daily job |
| Forced actions | Times a root admin used `force` on a protected brand | Proposed: access log |

## Open

1. **Protect the other four partners too?** Today only Arata is listed. Proposal: add each partner the day it connects its first real account, starting with Nutrova.
2. **Should `force` exist at all on production?** Proposal: keep it for Param only, log every use, and require a note saying why.
