system/house-burn-down.md
How to rebuild the McRitchie dev environment from a freshly-reset Mac. This is the detailed fallback behind mcritchie-studio/bin/ecosystem-build; start with the fast path and only drop into manual phases when a script phase fails.
Time budget: ~25-30 min on a fresh machine with Homebrew installed, longer on slow links or when Rust/Anchor dependencies compile cold. Later runs are usually under a minute because the script is idempotent.
| Repo | Role | Stack | Port |
|---|---|---|---|
mcritchie-studio |
Flagship hub. Task/News/Content pipelines, NFL data, agent docs, recovery scripts | Rails 7.2 / Postgres | 3000 |
turf-monster |
Sports pick'em (World Cup 2026). Solana onchain | Rails 7.2 / Postgres / Redis / Sidekiq | 3100 |
studio-engine |
Shared Rails engine: passwordless auth, error logging, theme, modals, ImageCache | Ruby gem | — |
solana-studio |
Ruby Solana client (RPC, ed25519, borsh, txns) | Ruby gem | — |
turf-vault |
Onchain escrow vault. 2-of-3 multisig. Consumed by Turf Monster | Anchor / Rust / Solana | — |
Dependency graph (build order):
studio-engine gem ──┐
├──> mcritchie-studio (flagship)
└──> turf-monster ──> solana-studio gem
──> turf-vault (devnet + mainnet deployments)
The Rails apps consume studio-engine and solana-studio from RubyGems. Local clones are still part of the rebuild because agents edit, release, and audit those gems.
On a fresh Mac with Homebrew already installed:
# 1. Clone the flagship — every other repo + script lives downstream of this one
git clone https://github.com/amcritchie/mcritchie-studio.git ~/projects/mcritchie-studio
cd ~/projects/mcritchie-studio
# 2. First pass — installs all brew packages (incl. 1Password CLI), Rust, Solana,
# Anchor, etc. Bails at Phase 4 once it needs your 1Password service token.
bin/ecosystem-build
# 3. Copy your 1Password service account token (ops_...) to clipboard, then:
bin/setup-1pass-token
# 4. Second pass — picks up at Phase 4 with the token now set, restores .env
# from Heroku + 1Password, clones the other 4 repos, bundles + DBs + Anchor,
# installs the root AGENTS.md, bounces both Rails servers.
bin/ecosystem-build
That's it. The only thing you actually do is copy the token and run the commands above. On every later machine a single pass just walks checkmarks and re-bounces the servers.
Re-running anytime: bin/ecosystem-build is fully idempotent. Run it after pulling new commits, switching branches, or anytime you want to confirm "everything still works." It will reset both Rails servers to a clean dev state.
Custom project layout: set PROJECTS_DIR to override ~/projects (e.g. PROJECTS_DIR=~/code bin/ecosystem-build). The script clones siblings into that directory.
Default NFL data: every build pulls the live schedule, runs the ESPN depth-chart scrape, and snapshots current-week rosters (~3-5 min). Game show pages and the season grid work out of the box.
Headshots (opt-in): /nfl-rosters shows position-labeled placeholders by default. To cache real player photos, run WITH_NFL_HEADSHOTS=1 bin/ecosystem-build — adds ~10-15 min for the nflverse master CSV + S3 headshot uploads. Requires AWS creds in .env (auto-restored by Phase 4).
The manual phase-by-phase steps below are kept as a fallback for debugging when the script can't complete a phase.
You need these already installed before this protocol can start:
xcode-select --install if missing)brew --version)git (ships with Xcode CLT)agents vault (account alex@mcritchie.studio / MWOV5OT5BRHATI4EGMN26C5DPA)bin/ecosystem-build handles everything else.
brew update && brew install \
ruby@3.1 \
mise \
postgresql@14 \
redis \
1password-cli \
gh \
imagemagick \
ffmpeg \
libpq \
heroku/brew/heroku
~5 min. Installs:
- ruby@3.1 — Ruby 3.1.7 with the full stdlib for McRitchie Studio and ecosystem bootstrap
- mise — version manager for Node (not Ruby — see above)
- postgresql@14 — local DB for both Rails apps
- redis — Sidekiq queue for Turf Monster and worktree stacks
- 1password-cli (op) — secret retrieval
- gh — GitHub CLI
- imagemagick — image resizing for studio engine's ImageCache
- ffmpeg — lineup-graphic video assembly (Starter Post X/TikTok workflows)
- libpq — pg gem build deps
- heroku — deploys
Start the background services:
brew services start postgresql@14
brew services start redis
The brew Postgres install creates a superuser matching your macOS login name with no password. Rails apps' database.yml uses default role + no password, so they connect fine without further setup.
Append to ~/.zshrc (idempotent — check before adding):
# Ruby via Homebrew (keg-only — must be added to PATH explicitly)
export PATH="/opt/homebrew/opt/ruby@3.1/bin:$PATH"
export PATH="/opt/homebrew/lib/ruby/gems/3.1.0/bin:$PATH"
# mise — Node (and other non-Ruby langs) version manager
eval "$(/opt/homebrew/bin/mise activate zsh)"
# Solana CLI
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
# Cargo (Rust)
export PATH="$HOME/.cargo/bin:$PATH"
Then either source ~/.zshrc or open a new terminal. Verify: which ruby → /opt/homebrew/opt/ruby@3.1/bin/ruby.
mise use --global node@22
npm install -g yarn
~30s. Node 22 is the ecosystem standard for local dev, CI, and Heroku asset builds. turf-vault's TypeScript test deps (@solana/codecs-numbers) need Node >= 20.18.0, so do not downgrade to Node 18.
Brew's ruby@3.1 formula gives you Ruby 3.1.7 with the complete stdlib. Verify:
ruby --version # ruby 3.1.7 (...)
ruby -e "require 'socket'; puts Socket.gethostname" # should print your hostname
If socket errors here, you're picking up the wrong Ruby — re-check Phase 2 PATH order.
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
source "$HOME/.cargo/env"
rustup install 1.89.0
rustup default 1.89.0
# Solana CLI (Anza — release.solana.com is deprecated)
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
# Anchor (uses Rust + cargo)
cargo install anchor-cli --version 0.32.1 --locked
~10-15 min. Anchor compile is the longest single step.
Gotcha: The Solana installer appends PATH to ~/.profile (bash convention), which zsh doesn't read by default. The PATH line in Phase 2's ~/.zshrc block handles this — don't skip it.
Verify:
bash
rustc --version # rustc 1.89.0
solana --version # solana-cli 3.x (Agave)
anchor --version # anchor-cli 0.32.1
solana-keygen new --no-bip39-passphrase --silent --outfile ~/.config/solana/id.json
solana config set --url devnet
solana address # confirm your new pubkey
This is a local dev keypair, NOT one of the agent vault wallets. The agent wallets (Alex Bot, Mason, Mack, Turf Monster) stay in 1Password.
mkdir -p ~/projects && cd ~/projects
gh repo clone amcritchie/mcritchie-studio
gh repo clone amcritchie/turf-monster
gh repo clone amcritchie/studio-engine
gh repo clone amcritchie/solana-studio
gh repo clone amcritchie/turf-vault
(gh auth login first if not authenticated.)
Two layers:
Use a service account token rather than the desktop app integration — it avoids Touch ID prompts per call and works headlessly. One-time setup:
alex@mcritchie.studio (account MWOV5OT5BRHATI4EGMN26C5DPA)agents vault (and 🧱 Blockchain if you'll need it)ops_... — only shown once)mcritchie-studio repo, run:bin/setup-1pass-token
The script reads from pbpaste, validates the prefix, strips whitespace, writes OP_SERVICE_ACCOUNT_TOKEN to ~/.zprofile (idempotent + chmod 600), and verifies with op vault list. Token never touches shell parsing — bypasses all the smart-quote / line-wrap / newline-in-paste failure modes that broke direct ! echo ops_… >> ~/.zprofile attempts. See Gotcha 12.
After it succeeds, source ~/.zprofile (or open a new terminal) to load the export.
To rotate the token later, just re-copy and re-run bin/setup-1pass-token — it replaces the existing line.
The Rails apps read .env via Rails' default dotenv (or the dotenv-rails gem). Restore each:
/Users/alex/projects/mcritchie-studio/.env — see docs/agents/modules/credentials.md for the operating rules and docs/agents/modules/credential-inventory.md for item names. Minimum to boot:
RAILS_MASTER_KEY=$(heroku config:get RAILS_MASTER_KEY --app mcritchie-studio)
GOOGLE_CLIENT_ID=... # Google Cloud Console
GOOGLE_CLIENT_SECRET=...
ANTHROPIC_API_KEY=... # 1Password: "anthropic" in agents vault
X_BEARER_TOKEN=... # 1Password: "x.api" (read)
X_API_KEY=... # 1Password: "x.api" (write — Read+Write app)
X_API_SECRET=...
X_ACCESS_TOKEN=...
X_ACCESS_TOKEN_SECRET=...
HIGGSFIELD_API_KEY=... # 1Password: "agent.higgesfield"
HIGGSFIELD_API_SECRET=...
TIKTOK_CLIENT_KEY=... # 1Password: "🐊 TikTok"
TIKTOK_CLIENT_SECRET=...
TIKTOK_REFRESH_TOKEN=...
TIKTOK_OPEN_ID=...
AWS_ACCESS_KEY_ID=... # S3 ImageCache bucket
AWS_SECRET_ACCESS_KEY=...
SES_AWS_ACCESS_KEY_ID=... # 1Password: agent.aws.mcritchie-ses, SES API checks only
SES_AWS_SECRET_ACCESS_KEY=...
SES_REGION=us-east-2
/Users/alex/projects/turf-monster/.env — see turf-monster/docs/SOLANA.md for full list. RAILS_MASTER_KEY is not optional — db:seed calls User#generate_managed_wallet! which reads Rails.application.credentials.secret_key_base to encrypt the wallet. Without the key, seed crashes mid-run with undefined method '[]' for nil:NilClass.
RAILS_MASTER_KEY=$(heroku config:get RAILS_MASTER_KEY --app turf-monster-mainnet)
GOOGLE_CLIENT_ID=... # may differ from mcritchie-studio
GOOGLE_CLIENT_SECRET=...
SOLANA_ADMIN_KEY=$(op item get "agent.alex.solana" --vault agents --fields "private key")
SOLANA_RPC_URL=https://api.devnet.solana.com # or paid provider if rate-limited
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
SES_AWS_ACCESS_KEY_ID=... # 1Password: agent.aws.mcritchie-ses, SES API checks only
SES_AWS_SECRET_ACCESS_KEY=...
SES_REGION=us-east-2
Shared /Users/alex/projects/.env — keep this minimal, only put truly cross-app vars here. Prefer app-local .env files plus the credential inventory for most secrets.
heroku login # opens browser
heroku apps # should list mcritchie-studio and turf-monster
This is what unlocks the heroku config:get commands above.
cd ~/projects/mcritchie-studio
bundle install
bin/rails db:create db:migrate db:seed
bin/rails server # port 3000
Visit http://localhost:3000 and sign in with a magic link to alex@mcritchie.studio.
Seeds load 9 agents (Alex, Avi, Carl, Shannon, Jasper, Steffon, Turf Monster, Mack, Mason), 35 skills, sample tasks, 32 NFL + 71 NCAA + 48 FIFA teams, ~2400 active contracts, ~570 PFF-graded athletes. The db:seed phase 32 (32_headshot_links.rb) makes network calls — safe to let it run, or skip with SKIP_NETWORK_SEEDS=1 if behind a firewall.
For full NFL data (UDFAs, depth charts, ESPN headshots cached to S3), use the NFL rebuild workflow once it has been promoted into neutral agent docs. The underlying steps are db:reset -> db:seed -> nfl:players_seed -> espn:scrape_depth_charts. Requires AWS creds in .env.
For the lineup capture (Starter Post X workflow) and Playwright e2e tests:
bash
npm install # installs Playwright + Chromium
npm test # 13 e2e smoke tests
cd ~/projects/turf-monster
bundle install
bin/rails db:create db:migrate db:seed
bin/tm up # starts/adopts web (3100), Sidekiq, Tailwind, Stripe listener
Visit http://localhost:3100. Login same admin.
bin/tm up is the agent-friendly command. It runs detached, preflights Redis/Postgres, builds Tailwind once, starts Sidekiq, starts the Stripe listener when available, polls readiness, and prints the review URL. bin/dev remains useful for a human interactive terminal with combined logs, but it can self-terminate in background/no-TTY sessions and does not manage the Stripe listener.
cd ~/projects/solana-studio
bundle install
ruby -Itest test/keypair_test.rb test/borsh_test.rb test/transaction_test.rb
Library only — no server. Only clone locally if editing.
cd ~/projects/studio-engine
bundle install
Engine only — no DB, no server, no tests of its own. Test it via the consuming apps' suites. Only clone locally if editing the engine.
After editing the engine and pushing to GitHub:
bash
cd ~/projects/mcritchie-studio && bundle update studio-engine
cd ~/projects/turf-monster && bundle update studio-engine
cd ~/projects/turf-vault
yarn install # TypeScript test deps (ts-mocha, @solana/codecs-*)
anchor build # ~3-5 min on first build
anchor test # spins up local validator and runs the TS suite
Current deployment facts drift quickly. Treat turf-vault/docs/CURRENT_DEPLOYMENT.md and turf-monster/docs/SOLANA.md as the source of truth for program IDs, clusters, and operator keys.
Gotcha: anchor test will fail with ts-mocha: command not found if you skip yarn install. The Anchor scaffold's test runner is JS, not Rust.
To deploy a new version: the program's upgrade authority is a Squads V4 2-of-3 multisig (squad vault PDA BW13kgfiG2koFn3WRkte21NW9TFygsD1ge2fNJdjH6kC), so a plain anchor deploy no longer works — it would be rejected by the on-chain upgrade-authority check. Build the program, then route the upgrade through the Squads multisig:
bash
solana config set --url devnet
anchor build
node turf-vault/scripts/squad-upgrade.js # writes the buffer + proposes the upgrade to the Squad for 2-of-3 cosign
See docs/agents/system/squads-upgrade-authority-migration.md for the historical migration notes, then verify current procedure in the turf-vault repo before proposing or approving an upgrade.
Season bootstrap and free-entry token operations now live with the app that runs them. Use turf-monster/docs/SOLANA.md, turf-monster/docs/AUTH.md, and the Turf Monster admin UI (/admin/seasons, /admin/free_entries) for current steps instead of copying version-specific instructions here.
/sso_login by design.solana address returns your pubkeycd ~/projects/turf-vault && anchor test — suite passesThese are the surprises from the last burn-down. Pre-baked into the steps above; documented here so future-you knows why:
Don't use mise/ruby-build for Ruby on Darwin 25 / Apple Silicon — mise auto-applies --with-ext=openssl,psych,+ which silently skips the socket C extension. Every bundle exec then dies with cannot load such file -- socket (LoadError). Reproduces on Ruby 3.1.0 AND 3.1.7. Fix: use brew install ruby@3.1 (always builds the full stdlib) and keep mise scoped to Node only. The .ruby-version files in both Rails apps say 3.1.0, but brew Ruby reports 3.1.7; bundler doesn't enforce patch level, so this is harmless.
release.solana.com is deprecated — returns SSL errors. Use release.anza.xyz/stable/install.
Solana installer writes PATH to ~/.profile — zsh doesn't source .profile by default. The Phase 2 ~/.zshrc block handles this explicitly.
Bundler version drift — Gemfile.lock pins BUNDLED WITH 2.4.19. mise's Ruby 3.1.7 ships with bundler 2.3.x and will auto-upgrade on first bundle install. Works fine if socket ext is present (Gotcha 1).
System Ruby 2.6 is unusable — macOS ships with ancient Ruby. Don't run bundle against it. mise's shims (~/.local/share/mise/shims) must be earlier on PATH than /usr/bin.
Heroku LFS gotcha — mcritchie-studio repo has LFS pointers in history (retired 2026-04-30) but Heroku's git remote doesn't speak LFS. Push with git push heroku main --no-verify to skip the LFS pre-push hook.
Public devnet RPC is rate-limited — SOLANA_RPC_URL defaults to public devnet which 429s under load. Use a paid provider (QuickNode, Helius) for serious work.
Sidekiq dies silently without Redis — Turf Monster's bin/tm up preflights Redis before starting Sidekiq. If running manually, check brew services list | grep redis first.
TikTok app is in review (submitted 2026-05-04) — only sandbox + manual posting paths work until Content Posting API approval. Don't expect API-direct posts to publish.
RAILS_MASTER_KEY is a hard prerequisite for turf-monster db:seed — not just for booting. db/seeds/users.rb calls User#generate_managed_wallet!, which encrypts the wallet's private key via Rails.application.credentials.secret_key_base[0, 32]. Without the master key, that returns nil and the seed dies. Restore the key before running db:create db:migrate db:seed.
Don't tail-pipe bin/rails db:seed — bin/rails db:seed | tail -30 returns tail's exit code (0), masking seed failures. Use set -o pipefail or run the seed without piping when verifying it ran clean.
macOS Terminal mangles long secret pastes — Multiple compounding failure modes when pasting tokens via shell commands:
" → " " on paste. zsh doesn't treat these as quote delimiters, so quoted strings get tokenized as bare words. Symptom: zsh: command not found: WRITE OK from echo "WRITE OK".chmod 600 into chmod 60 + \n + 0 filename. or literal spaces inside base64url tokens.
Solution: use bin/setup-1pass-token (which reads pbpaste → never goes through shell parsing). For one-off Heroku/etc. keys, use the same pattern: KEY=$(pbpaste | tr -d '[:space:]') rather than KEY=ops_paste_here.bin/ecosystem-build doesPhases execute in order. Each phase: detect current state → install/configure only what's missing → verify. Re-running is safe; on a healthy machine, every phase logs ✓ checkmarks.
| Phase | Responsibility |
|---|---|
| 1. System tools | Homebrew packages (ruby@3.1, postgres@14, redis, mise, gh, heroku, etc.), starts Postgres + Redis services, verifies ruby socket extension |
| 2. Languages | Node 22 + yarn (via mise), Rust 1.89.0 (via rustup), Solana CLI (via Anza), Anchor 0.32.1 (via cargo), local Solana devnet keypair |
| 3. Shell config | ~/.zshrc PATH lines (brew Ruby, mise activation, Solana, Cargo), ~/.zprofile chmod 600 |
| 4. Secrets | Verifies OP_SERVICE_ACCOUNT_TOKEN works; pulls agent.heroku from 1Password into HEROKU_API_KEY; restores .env for active Rails apps from provider config |
| 5. Sibling repos | gh repo clone for turf-monster, studio-engine, solana-studio, turf-vault (skips ones already present) |
| 5b. Agent docs | Installs /Users/alex/projects/AGENTS.md from mcritchie-studio/docs/agents/index.md |
| 5c. Secrets replay | Re-runs Phase 4 after sibling repos exist so newly-cloned active satellites get .env before DB setup |
| 6. Bundles + DBs | bundle install + db:create db:migrate db:seed for each Rails app; bundle for solana-studio |
| 6b. NFL data (default) | Always runs. Chains nfl:schedule_seed YEAR=2026 (real schedule from nflverse) + espn:scrape_depth_charts (live depth charts from ESPN JSON API) + nfl:rosters_snapshot SEASON=2026-nfl (snapshot fresh depth charts → current-week Rosters) + nfl:rankings_compute SEASON=2026-nfl GRADES_FROM=2025-nfl (preseason TeamRanking snapshot using last year's PFF grades, so /games/2026/week/N/... show pages render rank pills). ~3-5 min, network only — no AWS creds needed. |
| 6c. NFL headshots (opt-in) | Only runs when WITH_NFL_HEADSHOTS=1. Chains nfl:players_seed (nflverse master CSV ~24k rows + S3 headshot cache ~1100 athletes) + nfl:upload_headshots. ~10-15 min, requires AWS creds in .env. Without this, /nfl-rosters shows position-labeled placeholder circles instead of player photos. |
| 7. Anchor + e2e | yarn install + anchor build for turf-vault; npm install for both Rails apps; npx playwright install chromium (~90 MB cached for e2e tests) |
| 8. Servers | Always kills + restarts active Rails apps on their registered ports, curls each to verify HTTP 2xx/3xx |
| 9. Env snapshot | Writes mcritchie-studio/tmp/env-snapshot-YYYY-MM-DD.json containing both apps' .env contents (raw, faithfully). Heroku-independent fallback for secret recovery. Gitignored, chmod 600. Skipped silently if no .env files exist yet. |
If any phase fails, the script prints what to do and exits. Re-running picks up where it left off.
The one secret-input boundary is Phase 4: if the OP token isn't set, the script bails with instructions to run bin/setup-1pass-token first. That's the only step that can't be automated — by design (the token has to come from your clipboard).
What this protocol installed last successful run:
| Tool | Version | Source |
|---|---|---|
| Homebrew | latest | pre-existing |
| mise | latest | brew |
| Ruby | 3.1.7 | brew ruby@3.1 |
| Node | 22.x | mise |
| yarn | 1.22.x | npm -g |
| Postgres | 14.x | brew (postgresql@14) |
| Redis | latest | brew |
| Rust | 1.89.0 | rustup (pinned) |
| Solana CLI | 3.1.x (Agave) | release.anza.xyz |
| Anchor | 0.32.1 | cargo |
| Bundler | 2.4.19 | auto-upgraded by Gemfile.lock |
| Heroku CLI | latest | brew |
| 1Password CLI | latest | brew |
docs/agents/system/bootstrap.md — first-time setup (one app, simpler)docs/agents/modules/credentials.md — credential rules and 1Password operating modeldocs/agents/modules/credential-inventory.md — known 1Password item namesdocs/agents/system/news-pipeline.md — News pipeline + X API setupRUNBOOK.md (top-level) — production troubleshooting (Heroku deploys, theme cache, SSO, OAuth)turf-monster/docs/SOLANA.md — Solana integration deep diveturf-vault/README.md — Anchor program structure + multisigWe emailed a one-tap sign-in link to . It expires shortly and can only be used once.
No email? Check spam, or close this and try again.