{
 "id": "8.10",
 "title": "Protected connections",
 "department": "Ops and infrastructure",
 "department_slug": "ops-and-infrastructure",
 "direction": null,
 "branded": false,
 "status": "draft",
 "owner": "Param",
 "last_verified": "2026-10-26",
 "holds": "Arata's Meta and Shopify, the guard, never disconnect",
 "urls": {
  "html": "https://handbook.joinsquareup.com/ops-and-infrastructure/protected-connections",
  "markdown": "https://handbook.joinsquareup.com/ops-and-infrastructure/protected-connections.md",
  "json": "https://handbook.joinsquareup.com/ops-and-infrastructure/protected-connections.json"
 },
 "other_directions": [],
 "tldr": {
  "headline": "Arata's Meta and Shopify are never disconnected, and code refuses to try",
  "points": [
   {
    "icon": "lock",
    "text": "PROTECTED_CONNECTIONS makes the code refuse disconnect, reconnect and re-select for listed brands."
   },
   {
    "icon": "link",
    "text": "Arata is connected on both sites because the vault was copied, never reconnected."
   },
   {
    "icon": "warn",
    "text": "The guard cannot stop revoking the app at Meta or Shopify itself."
   },
   {
    "icon": "check",
    "text": "Ping jobs read the sign-ins without printing any token."
   },
   {
    "icon": "people",
    "text": "Only Arata is protected today; the other four partners are not."
   }
  ],
  "stat": {
   "value": "22 Oct",
   "label": "Param's rule on Arata's Meta"
  },
  "decide": "Protect the other four partners too, and keep force on production?",
  "mood": "waiting"
 },
 "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)"
 ],
 "open_questions": [
  "**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.",
  "**Should `force` exist at all on production?** Proposal: keep it for Param only, log every use, and require a note saying why."
 ],
 "parts": {
  "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\n\n- Never disconnect, reconnect or re-select Arata's Meta connector, in any case. Param, 22 Oct. [src: memory never-disconnect-arata-meta]\n- Never disconnect Arata's Shopify. [src: memory brew-shopify-connector-qa]\n- Never revoke Meta grants. [src: memory brew-meta-connector-qa]\n- 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]\n- Disconnect and reconnect tests run only on Cozy Snugglet. [src: memory never-disconnect-arata-meta] [src: memory brew-live-stores]\n\n### What Arata has connected\n\n| Account | How | Why it can be shared by both sites | Source |\n|---|---|---|---|\n| 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] |\n| 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] |\n\nIf Arata's Shopify token ever starts rotating, production becomes the only refresher and staging uses a token broker. [src: memory brew-prod-backend]\n\n### The guard in code\n\n| Layer | What it refuses | Source |\n|---|---|---|\n| `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] |\n| Connector routes | `disconnect`, `select`, `key`, `connect` on an existing protected connection, unless a root admin sends `force: true` | [src: brew/connectors/index.js:429] |\n| Replacing a connection | Refused for a protected brand unless forced by a root admin | [src: brew/connectors/index.js:148] |\n| Disconnect function | Refused for a protected brand unless forced | [src: brew/connectors/index.js:269] |\n| Vault | A protected brand's stored sign-in is never replaced, except a refresh or a force | [src: brew/vault.js:27] |\n| 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] |\n| 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] |\n| 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] |\n| 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] |\n| Shopify webhooks | Uninstall and redact disconnect only `app:'brew'` connections, so Arata's own app is untouched | [src: memory brew-shopify-connector-qa] |\n| 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] |\n\n### What the guard does not cover\n\n- Provider-side grants: revoking the app at Meta or Shopify is outside our code. [src: memory brew-wipe-data]\n- 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]\n- A deletion never revokes a platform grant; it prints the grants the brand revokes itself. [src: brew/db/brand-wipe.js:7, 240]\n- The `force` path is open to root admins. [src: brew/connectors/index.js:429]",
  "Tomorrow": "| Item | Version | Tag |\n|---|---|---|\n| Arata protected on both sites | v1 | Stays |\n| Every design partner protected once its accounts are connected | v1 | Changes, see Open |\n| Wipe tools that refuse protected brands in code, on origin/main since 26 Oct | v1 | Stays |\n| A daily ping of protected sign-ins, alerting when one stops reading | v1 | New |\n| 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.\n2. Check the connections still read, read only, no token printed:\n\n```bash\ngcloud run jobs execute brew-prod-ping --region=asia-south1 --wait\ngcloud run jobs execute brew-staging-ping --region=asia-south1 --wait\ngcloud run jobs execute brew-ops --region=asia-south1 --wait\n```\n\nThe 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]\n3. 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]\n4. 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 |\n|---|---|---|\n| Protected sign-ins reading | Protected accounts whose ping succeeds, out of all | `db/connector-ping.mjs`; Proposed as a daily job |\n| 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.\n2. **Should `force` exist at all on production?** Proposal: keep it for Param only, log every use, and require a note saying why."
 }
}