{
 "id": "8.1",
 "title": "Runbook index",
 "department": "Ops and infrastructure",
 "department_slug": "ops-and-infrastructure",
 "direction": null,
 "branded": false,
 "status": "draft",
 "owner": "Param",
 "last_verified": "2026-10-26",
 "holds": "Every runbook and when to use it",
 "urls": {
  "html": "https://handbook.joinsquareup.com/ops-and-infrastructure/runbook-index",
  "markdown": "https://handbook.joinsquareup.com/ops-and-infrastructure/runbook-index.md",
  "json": "https://handbook.joinsquareup.com/ops-and-infrastructure/runbook-index.json"
 },
 "other_directions": [],
 "tldr": {
  "headline": "Every runbook in one list, so nobody runs a script from memory",
  "points": [
   {
    "icon": "book",
    "text": "Runbooks are grouped by releasing, restoring and checking, and wiping and resetting."
   },
   {
    "icon": "flag",
    "text": "Check which site a script names before running it; staging and production differ."
   },
   {
    "icon": "warn",
    "text": "tools/reset-jams.sh still edits the old bucket, though staging keeps records in Postgres."
   },
   {
    "icon": "rocket",
    "text": "New: a brew-prod-ops job for deletions on production, and retire three bucket scripts."
   }
  ],
  "stat": {
   "value": "1",
   "label": "Stale runbook today"
  },
  "decide": "Who may run production runbooks, and should the bucket scripts be deleted now?",
  "mood": "waiting"
 },
 "sources": [
  "brew origin/main a559de6: deploy.sh:1-53, tools/promote.sh:1-119, tools/prod-env.sh:1-13, tools/flush-live.sh:1-26, tools/reset-brand.sh:1-20, db/reset-brand.mjs:1-13, db/delete-brand.mjs:1-12, tools/reset-jams.sh:1-30, tools/partner-copy.sh:1-22, tools/deploy-demo.sh:1-26, tools/demo-sync.sh:1-30, db/reset-jams.mjs:1-30, db/status.mjs:1-27, db/connector-ping.mjs:1-24, db/parity.mjs:1-30, db/import-bucket.mjs:1-30, db/migrate.js:1-27",
  "brew/.wt-prod/docs/production.md:23-81",
  "brew origin/main d6154b3: docs/production.md:66-110, .github/workflows/test.yml:1-37",
  "brand-directions/_tools/deploy-handbook.py:1-115, brand-directions/_tools/deploy-docs.py:1-60",
  "memory brew-prod-backend, brew-wipe-data, brew-jam-reset, brew-dns-local, brew-parallel-sessions-deploy, brew-storage"
 ],
 "open_questions": [
  "**Who may run production runbooks?** Today the scripts run with whoever's gcloud sign-in is on the Mac. Proposal: only Param runs `promote.sh` and the restore runbooks until a second person has done one restore test with him.",
  "**Should the bucket scripts be deleted now?** `tools/reset-brand.sh` and `tools/flush-live.sh` already stop on `STORAGE=pg`. Proposal: give `tools/reset-jams.sh` the same first lines, and delete all three once production has run on Postgres for a month."
 ],
 "parts": {
  "Answer": "This is the list of every operational runbook we have, what each one is for and when to reach for it, so nobody runs a script from memory or picks the wrong one for the site in front of them.",
  "Today": "### Releasing\n\n| Runbook | Use it when | Site | Where it is written |\n|---|---|---|---|\n| `./deploy.sh` | A change is tested and committed; deploy right away | Staging | [Deploy and promote](/ops-and-infrastructure/deploy-and-promote) [src: brew/deploy.sh:1-7] |\n| `tools/promote.sh` | Staging runs what production should run | Production | [Deploy and promote](/ops-and-infrastructure/deploy-and-promote) [src: brew/tools/promote.sh:1-9] |\n| `tools/deploy-demo.sh` | The demo needs new code | Demo | [src: brew/tools/deploy-demo.sh:1-6] |\n| `tools/demo-sync.sh <slug>` | A prepared brand's public pack goes to the demo | Demo | [src: brew/tools/demo-sync.sh:1-7] |\n| `tools/partner-copy.sh <slug> apply` | A partner's data is copied once from staging to production | Staging to production | [src: brew/tools/partner-copy.sh:1-7] |\n| `python3 brand-directions/_tools/deploy-handbook.py` | The handbook changed and `check.mjs` passes | Handbook | [Hosting map](/ops-and-infrastructure/hosting-map) [src: brand-directions/_tools/deploy-handbook.py:1-10] |\n| `python3 brand-directions/_tools/deploy-docs.py` | The shared docs changed | Docs | [src: brand-directions/_tools/deploy-docs.py:1-4] |\n\n### Restoring and checking\n\n| Runbook | Use it when | Site | Where it is written |\n|---|---|---|---|\n| Clone to a moment | A bad migration or mass damage on production | Production | [Restore](/ops-and-infrastructure/restore) [src: brew/.wt-prod/docs/production.md:47-50] |\n| One brand back | One brand deleted or damaged by mistake | Production | [Restore](/ops-and-infrastructure/restore) [src: brew/.wt-prod/docs/production.md:52-55] |\n| Restore test | Monthly | Production into staging | [Restore](/ops-and-infrastructure/restore) [src: brew/.wt-prod/docs/production.md:59-64] |\n| `db/status.mjs` through job `brew-ops` | Is Arata still connected, how big are the tables, are jobs stuck | Staging | [src: brew/db/status.mjs:1-2] [src: memory brew-prod-backend] |\n| `db/connector-ping.mjs` through jobs `brew-prod-ping` and `brew-staging-ping` | Do a brand's stored sign-ins still read, after a restore or a copy | Either | [Protected connections](/ops-and-infrastructure/protected-connections) [src: brew/db/connector-ping.mjs:1-4] [src: memory brew-prod-backend] |\n| `db/parity.mjs` | Does Postgres hold everything the bucket holds | Staging | [src: brew/db/parity.mjs:1-3] |\n| `db/import-bucket.mjs` through job `brew-import` | After any staging deploy that ran on the bucket | Staging | [src: brew/db/import-bucket.mjs:1-6] [src: memory brew-storage] |\n| Three DNS checks | Someone says the site is down | Any | [DNS and domains](/ops-and-infrastructure/dns-and-domains) [src: memory brew-dns-local] |\n\n### Wiping and resetting\n\n| Runbook | Use it when | Site | Where it is written |\n|---|---|---|---|\n| `db/reset-jams.mjs` through job `brew-ops` | Param asks to start the jams fresh | Staging, on Postgres | [Wipe data and reset jams](/ops-and-infrastructure/wipe-data-and-reset-jams) [src: brew/db/reset-jams.mjs:1-7] |\n| \"Wipe data\" | Param says \"wipe data\" for a test brand | Staging | [Wipe data and reset jams](/ops-and-infrastructure/wipe-data-and-reset-jams) [src: memory brew-wipe-data] |\n| `db/reset-brand.mjs` through job `brew-ops` | One brand back to a fresh first run, connections kept; also the way to flush every brand, one slug at a time | Staging | [Wipe data and reset jams](/ops-and-infrastructure/wipe-data-and-reset-jams) [src: brew/db/reset-brand.mjs:1-11] |\n| `db/delete-brand.mjs` through job `brew-ops`, or `brew-prod-ops` once made | A brand asks us to delete its data | Staging; production after the next promote | [Data requests](/customer-success-and-support/data-requests) [src: brew/db/delete-brand.mjs:1-10] [src: brew/docs/production.md:95-106] |\n\n### Scripts that no longer fit the site they name\n\n- `tools/reset-jams.sh` edits `gs://brew-prototype` directly. Staging has kept its records in Postgres since 25 Oct, so it edits the mirror, not the truth. [src: brew/tools/reset-jams.sh:6] [src: memory brew-jam-reset]\n- `tools/reset-brand.sh` and `tools/flush-live.sh` are bucket era too, but since 26 Oct they exit 3 unless `STORAGE=gcs` and print the `brew-ops` command to run instead. [src: brew/tools/reset-brand.sh:11-19] [src: brew/tools/flush-live.sh:17-26]\n\n### Checking\n\n| Runbook | Use it when | Site | Where it is written |\n|---|---|---|---|\n| GitHub Actions workflow `test` | Every push and pull request runs the deploy test gate on Node 22 with Postgres 17 | GitHub | [Testing and QA](/engineering/testing-and-qa) [src: brew/.github/workflows/test.yml:1-37] |",
  "Tomorrow": "| Item | Version | Tag |\n|---|---|---|\n| One index of runbooks, each linked to its page | v1 | Stays |\n| A Postgres brand reset and deletion, run as `brew-ops` jobs, on origin/main | v1 | Stays |\n| A `brew-prod-ops` job on production for deletions | v1 | New |\n| Retire the three bucket scripts; two already refuse on Postgres | v1 | Goes |\n| Every runbook printed from code into this index on each build | v2 | New |",
  "How we do it": "1. Find the job in the tables above, then open its page. Never run a script from memory.\n2. Check which site the script names before running it. Staging scripts hard-code `shopos-feed` and `brew.joinsquareup.com`; production scripts source `tools/prod-env.sh`.\n3. Run cloud scripts from a clean, detached worktree of origin/main, never the shared checkout:\n\n```bash\ngit -C /Users/param/Documents/ChatGPT/brew fetch -q origin\ngit -C /Users/param/Documents/ChatGPT/brew worktree add --detach \"$TMPDIR/brew-ops\" origin/main\n```\n\n4. Job arguments with flags use gcloud's `\"--args=^:^a:b:c\"` form. [src: memory brew-prod-backend]\n5. When a runbook changes, update its page and this index in the same session.",
  "Numbers": "| Metric | Definition | Where measured |\n|---|---|---|\n| Runbooks without a page | Scripts in `tools/` or `db/` that this index does not list | This page against `git ls-tree origin/main tools/ db/`, Proposed |\n| Stale runbooks | Scripts that target storage the site no longer uses and still run | This page, 1 today, `tools/reset-jams.sh` |",
  "Open": "1. **Who may run production runbooks?** Today the scripts run with whoever's gcloud sign-in is on the Mac. Proposal: only Param runs `promote.sh` and the restore runbooks until a second person has done one restore test with him.\n2. **Should the bucket scripts be deleted now?** `tools/reset-brand.sh` and `tools/flush-live.sh` already stop on `STORAGE=pg`. Proposal: give `tools/reset-jams.sh` the same first lines, and delete all three once production has run on Postgres for a month."
 }
}