{
 "id": "5.15",
 "title": "Decision records",
 "department": "Engineering",
 "department_slug": "engineering",
 "direction": null,
 "branded": false,
 "status": "draft",
 "owner": "Param",
 "last_verified": "2026-10-26",
 "holds": "One per big technical call",
 "urls": {
  "html": "https://handbook.joinsquareup.com/engineering/decision-records",
  "markdown": "https://handbook.joinsquareup.com/engineering/decision-records.md",
  "json": "https://handbook.joinsquareup.com/engineering/decision-records.json"
 },
 "other_directions": [],
 "tldr": {
  "headline": "Big technical calls written down, so nobody re-decides without new facts",
  "points": [
   {
    "icon": "flag",
    "text": "DR-1: a realistic prototype, cut to read, Plays, feed, jam and brief."
   },
   {
    "icon": "lock",
    "text": "DR-2: read only connectors built by us, with no vendor."
   },
   {
    "icon": "money",
    "text": "DR-3: Gemini Flash for most work, Gemini Pro for Plays, on Google credits."
   },
   {
    "icon": "clock",
    "text": "DR-4: deploys never wait; runs resume on the next instance."
   },
   {
    "icon": "warn",
    "text": "The launch model for Plays is still open."
   }
  ],
  "stat": {
   "value": "2028-05-19",
   "label": "Google credits end"
  },
  "decide": "Which model writes Plays at launch? Proposal: Opus with credit, after a blind test.",
  "mood": "building"
 },
 "sources": [
  "memory brew-storage, brew-prod-backend, brew-real-connectors, brew-gemini-provider, brew-pro-switch-and-flush, brew-model-compare-2026-10-20, brew-jam-v2, brew-v1-creative-stage, brew-one-stage, never-disconnect-arata-meta, prod-clean-only, brew-prototype-repo, keep-all-moves, brew-one-version, brew-sloosh-finals, brew-brief-mode, brew-feed-open-moves, brew-qa-round2-2026-10-24, brew-lean-pipeline",
  "brew origin/main git log: 9ee9d14, 119dc8a, deb7d90, 522662c (per memory), 1afb9ba (per memory)",
  "brew origin/main: SCOPE.md, CLAUDE.md, roles.js, jobs.js, connectors/protected.js, connectors/shopify.js:13, moves-engine.js:17, deploy.sh:39, tools/prod-env.sh:9, db/prod-clean.mjs"
 ],
 "open_questions": [
  "**Which model writes Plays at launch?** Proposal: Opus with Anthropic credit, after a 5-Play blind test against Pro (about $1).",
  "**Should DR-17 also cover Sloosh finals on production?** Proposal: yes, keep finals on Gemini on production until Param says otherwise."
 ],
 "parts": {
  "Answer": "One record per big technical call, each with its context, the decision, what follows from it and the date, so no session re-decides a settled thing without new facts.",
  "Today": "### DR-1. A realistic prototype, cut to one flow\n\n- **Date:** 15 Oct 2026. [src: memory brew-prototype-repo]\n- **Context:** the old `feed/` repo had grown Do it now, measurement, sending and Arata-as-code. Param: \"we are building a realistic prototype\".\n- **Decision:** a new repo `brew/` with one flow, read, Plays, feed, jam, share the brief. Cut: Do it now, writing to accounts, sending to customers, measurement, audit Plays, the free cap. Dummies are tagged in three places. [src: brew/SCOPE.md] [src: brew/CLAUDE.md]\n- **Consequences:** `feed/` archived (tag `archive-2026-10-15`); old surfaces retired; leftover code from cut features remains (see Tech debt).\n\n### DR-2. Read-only connectors, built by us\n\n- **Date:** 16 Oct 2026. [src: memory brew-real-connectors]\n- **Context:** real brand data was the biggest blocker; Composio was proposed.\n- **Decision:** no vendor. Revive our own connectors, read-only scopes only; Brew never writes to an account. [src: brew/SCOPE.md] [src: brew/connectors/shopify.js:13]\n- **Consequences:** each platform's approval gates us directly (Google verified 21 Oct; Shopify submitted 21 Oct; Meta blocked on business verification). Do not propose vendors again without new facts.\n\n### DR-3. Gemini Flash while building, Pro for Plays\n\n- **Date:** 16 Oct 2026 (Flash), 20 Oct 2026 (Pro for Plays). [src: memory brew-gemini-provider] [src: memory brew-pro-switch-and-flush]\n- **Context:** Anthropic credit at $0; Google credits about $20,763 of $25,000 left, ending 2028-05-19.\n- **Decision:** `AI_PROVIDER=gemini`; `gemini-3.5-flash` for everything except Plays, which use `GEMINI_MOVES_MODEL=gemini-3.1-pro-preview`. `AI_PROVIDER=anthropic` switches back. [src: brew/moves-engine.js:17] [src: brew/deploy.sh:39]\n- **Consequences:** Plays are shorter and more generic than Opus (20 Oct: median 2,250 vs 5,450 words); the weekly rewrite pot is $40 on Pro, $10 on Flash. The launch model is still open.\n\n### DR-4. Deploys never wait\n\n- **Date:** 16 Oct 2026, commit 522662c. [src: memory brew-pro-switch-and-flush]\n- **Context:** a deploy guard waited for background work; Param: \"continuous deployment is part of development\".\n- **Decision:** remove the guard; SIGTERM stops new work and the next instance resumes. [src: brew/CLAUDE.md]\n- **Consequences:** every background run must be resumable and never pay twice; never re-add a wait or gap.\n\n### DR-5. Every Play is kept; make each cheaper\n\n- **Date:** 15 Oct 2026. [src: memory keep-all-moves]\n- **Context:** a \"planner picks about 10 Plays\" option was proposed to save cost.\n- **Decision:** keep all Plays; cut tokens, caching and repeat evidence calls; PDFs only when opened. [src: memory brew-lean-pipeline]\n- **Consequences:** cost work targets per-Play spend and the rewrite pot.\n\n### DR-6. Sloosh makes finals, quote first\n\n- **Date:** 16 Oct 2026. [src: memory brew-sloosh-finals]\n- **Decision:** drafts stay on Gemini; Sloosh makes go-live finals only behind Make the final, after a quote and a yes; chat never spends; Gemini fallback while the workspace cannot pay. [src: brew/SCOPE.md]\n- **Consequences:** finals keys written before paying; production runs with Sloosh off (25 Oct). [src: git 0fc7cfc]\n\n### DR-7. One version, then Redo it\n\n- **Date:** 19 Oct 2026. [src: memory brew-one-version]\n- **Decision:** never several options to pick from; one version and Redo. A/B decisions and test batches are not options.\n- **Consequences:** `MAKE_N` 1; saved \"3 versions\" text heals on read.\n\n### DR-8. Plays start on the first feed open\n\n- **Date:** 22 Oct 2026. [src: memory brew-feed-open-moves]\n- **Decision:** a brand's first read writes Plays only after someone opens the feed after onboarding (`feedGate`). [src: brew/SCOPE.md]\n- **Consequences:** corrections made while waiting reach the brief first; two reminder emails.\n\n### DR-9. Protected connections\n\n- **Date:** 22 Oct 2026 (rule), 25 Oct 2026 (in code, rev 00710). [src: memory never-disconnect-arata-meta] [src: memory brew-prod-backend]\n- **Context:** Arata is the client going live; its Meta account act_2942894532542334 drives the private rewrite of its Plays.\n- **Decision:** never disconnect, re-select or reconnect Arata's Meta or Shopify. `PROTECTED_CONNECTIONS` (default `arata-in`) refuses those actions unless a root admin forces it; the vault refuses to drop the token. [src: brew/connectors/protected.js]\n- **Consequences:** disconnect tests run on Cozy; Arata's vault is shared between staging and production rather than reconnected.\n\n### DR-10. Postgres over the bucket\n\n- **Date:** 16 Oct 2026 (not yet, not Supabase), 25 Oct 2026 (Cloud SQL, approved and live). [src: memory brew-storage] [src: memory brew-prod-backend]\n- **Context:** whole-file writes to `gs://brew-prototype` were last-write-wins and safe only with exactly one instance.\n- **Decision:** Cloud SQL Postgres in asia-south1 on Google credits, not Supabase. First step: every structured object as a row (`objects`), big documents in parts (`doc_parts`), binaries in the bucket; the bucket mirrored as the way back. [src: brew/db/migrations/001_objects.sql] [src: brew/db/migrations/004_doc_parts.sql]\n- **Consequences:** more than one instance became possible; per-domain tables are still to come; the bucket-era tools (`reset-jams.sh`, `reset-brand.sh`) no longer work on Postgres sites; since 26 Oct `db/reset-brand.mjs` and `db/delete-brand.mjs` replace the brand ones, which now exit 3 unless `STORAGE=gcs` [src: brew/tools/reset-brand.sh:11-19; brew/db/reset-brand.mjs:1-11].\n\n### DR-11. Web and worker roles with one lease holder\n\n- **Date:** 25 Oct 2026 (Phase 2, rev 00707). [src: memory brew-prod-backend]\n- **Decision:** `ROLE=web|worker`; one worker holds a Postgres advisory-lock lease and alone runs timers, resumes and paid watchers; web instances ask the worker through a Postgres job queue. [src: brew/roles.js:1-9] [src: brew/jobs.js:1-8]\n- **Consequences:** production runs 2 to 10 web instances and exactly 1 worker until per-brand build leases exist.\n\n### DR-12. Production on feed.joinsquareup.com, staging on brew\n\n- **Date:** 25 Oct 2026. [src: memory brew-prod-backend]\n- **Decision:** production is `brew-web` plus `brew-worker` on its own database, bucket and identity; staging is `shopos-feed`; production is released only by promoting staging's exact image. [src: brew/.wt-prod/docs/production.md]\n- **Consequences:** two sites to keep in step for sign-in callbacks and Slack; a moved-brand rule decides each brand's home.\n\n### DR-13. Nothing dummy or test reaches production\n\n- **Date:** 25 Oct 2026. [src: memory prod-clean-only]\n- **Decision:** production never runs demo, mock, dev sign-in, open-site or simulated connectors; a clean gate in every promote refuses any finding. Four kept exceptions decided one by one. [src: brew/db/prod-clean.mjs:1-6]\n- **Consequences:** test brands stay on staging; partner copies exclude testing data.\n\n### DR-14. Every partner's home is production\n\n- **Date:** 25 Oct 2026, 20:40 IST, commit 119dc8a (it reversed deb7d90 from 20:11). [src: git 119dc8a, deb7d90]\n- **Decision:** Arata, Nutrova, Mana, PUMA India and Scrubroom onboard on production; staging lists all five in `MOVED_TO_PROD`; production's `STAGING_HOME` is empty. [src: brew/deploy.sh:39] [src: brew/tools/prod-env.sh:9]\n- **Consequences:** staging is for testing only; the reverse list stays in code, empty.\n\n### DR-15. Retire Jam v2's own screen\n\n- **Date:** 25 Oct 2026, commit 9ee9d14. [src: git 9ee9d14] [src: memory brew-one-stage]\n- **Context:** v2 was locked behind `?v=2` on 22 Oct; on 23 Oct Param ruled no v2 jam inside v1; two jams confused people.\n- **Decision:** remove `?v=2`, `window.Jam2` and `public/jam2/jam2.js`; every card opens v1's jam, whose Set-up drives the `jam2/` Studio engine.\n- **Consequences:** `jam2.css`, `scene.js` and the `/api/jam2` routes stay; v2-only routes remain mounted until shared links are checked.\n\n### DR-16. No Gmail scope for briefs\n\n- **Date:** 23 Oct 2026. [src: memory brew-brief-mode]\n- **Decision:** briefs open in Gmail by a compose page with the files downloaded, or as an .eml draft; no `gmail.compose`, which is a restricted scope needing a yearly assessment.\n- **Consequences:** Google verification stays at two scopes.\n\n### DR-17. Brew stays independent of the sister company\n\n- **Date:** 24 Oct 2026. [src: memory brew-qa-round2-2026-10-24]\n- **Decision:** no new ties to the sister company or its studio, Sloosh; existing ones (Send brief to our team, Sloosh finals, the staff domains) stay.\n- **Consequences:** new features never depend on those services.",
  "Tomorrow": "| Item | Version | Tag |\n|---|---|---|\n| A record for every new big call, written the day it is made | v1 | New |\n| DR-3 revisited before client launch (launch model) | v1 | Changes |\n| DR-11 extended with per-brand build leases | v2 | Changes |",
  "How we do it": "1. A decision is big if it changes storage, roles, a provider, scope, access or what reaches production.\n2. Write the record the same day with context, decision, consequences and date, each sourced; add it to `content/company-and-strategy/decisions-log.md` too.\n3. A record is replaced only by a newer record that names it; never edited away.",
  "Numbers": "Not applicable: decisions are records, not metrics.",
  "Open": "1. **Which model writes Plays at launch?** Proposal: Opus with Anthropic credit, after a 5-Play blind test against Pro (about $1).\n2. **Should DR-17 also cover Sloosh finals on production?** Proposal: yes, keep finals on Gemini on production until Param says otherwise."
 }
}