chore(infra): make a fresh clone startable (#315) - #333
Merged
Conversation
Seven undocumented manual fixes stood between a clean clone and a working local stack. Six remained (the configurable Postgres host port landed in #316); this closes them. - `pnpm setup:env` writes .env, apps/api/.env and apps/mobile/.env from their examples and generates real JWT secrets, replacing the `<generate-with-...>` placeholders that only failed env.ts's `min(8)` AFTER boot. Idempotent. - `.nvmrc` pins 22 and `engine-strict=true` turns pnpm's soft `Unsupported engine` warning into a hard failure naming the required version. - turbo's `dev` task gains `dependsOn: ["^build"]`, so @thebrain/types and @thebrain/core compile before any app boots instead of dying with an ERR_MODULE_NOT_FOUND that names an *app* file. - `pnpm --filter @thebrain/api promote -- <email> <role>` bootstraps the first admin/superadmin through `adminRepository.updateRole` rather than the raw `UPDATE users SET role=…` that invariant §2.1 forbids; `SEED_ROLE` does the same at seed time. Both refuse to run in production. - the seeded demo password is now >= 8 chars in BOTH seed paths (`seed` and `seed:persona`, which had the same 4-char literal), so the account satisfies `registerRequestSchema` and is reproducible through the public API. - Android toolchain auto-provisioning is disabled, so a missing JDK 17 fails with Gradle's actionable "no matching toolchain" message instead of the foojay resolver's `NoSuchFieldError: ... IBM_SEMERU`. Docs: a "fresh machine" section in CLAUDE.md/AGENTS.md, a new apps/mobile/README.md covering the JDK 17 requirement, and the `dev` skill now calls setup:env instead of exiting when .env is absent. Closes #315 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A clean clone of TheBrain could not reach a working local stack. #315 catalogued
seven manual fixes standing in the way; one (the configurable Postgres host port)
landed separately in #316, so this closes the remaining six — plus the docs so the
next person doesn't rediscover them.
Phase 1's exit gate (#204) is now dogfooding-blocked, not code-blocked — four
of its five §13.2 criteria close only by running the system over time. That makes
"can you start the system at all" the highest-leverage thing in the repo, which is
why this is P1 rather than housekeeping.
The six fixes
.envanywhere;apps/api/.env.exampleshipped<generate-with-openssl-rand-hex-32>placeholders that only failedenv.ts'smin(8)after bootpnpm setup:envwrites all three env files and substitutes real 32-byte hex secrets. Idempotent — never overwrites, so re-running preserves your API keysUnsupported enginewarning and installed anyway.nvmrcpins 22;engine-strict=truemakes it a hard, named failurepnpm devon a clean checkout died withERR_MODULE_NOT_FOUNDnaming an app file, reading as a broken importdevtask gainsdependsOn: ["^build"]NoSuchFieldError: JvmVendorSpec ... IBM_SEMERU— naming neither cause nor fixapps/mobile/README.mddocuments JDK 17 + the exactJAVA_HOMEinvocationUPDATE users SET role='admin' …— the exact invariant §2.1 bypasspnpm --filter @thebrain/api promote -- <email> <role>goes throughadminRepository.updateRole;SEED_ROLEdoes the same at seed time. Both refuse to run in productiondemo(4 chars), whichregisterRequestSchemarejects (min(8)) — the account was usable but not reproducible through any public endpointFinding 7 turned out to exist in two places:
scripts/seed.tsandsrc/dev/seed-synthetic.ts(seed:persona) each hardcoded the same 4-charliteral for the same email. Both now share one
DEMO_CREDENTIALSconstant, sothey can't drift apart again.
Finding 2's compose change was already on main; what was missing was any mention
of
POSTGRES_PORTin the env examples, so nobody could discover the knob. Added.One deliberate deviation from the issue
#315 suggested pinning
foojay-resolver-convention≥1.0.0 viapluginManagementin
apps/mobile/android/settings.gradle. That cannot work. The 0.5.0 pin livesin
@react-native/gradle-plugin/settings.gradle.kts, which is pulled in withincludeBuild(...)— an included build resolves plugins through its ownpluginManagementscope, so the consuming project cannot override it. (It's alsoa
node_modulesfile, so editing it isn't durable.)Disabling toolchain auto-provisioning attacks the actual trigger instead: the
resolver is only ever invoked to auto-download a missing JDK 17, so with
auto-download off it is never touched, and the failure becomes Gradle's own
"No matching toolchain ... auto provisioning is disabled".
Acceptance criteria
pnpm install && pnpm setup:env && pnpm devbrings up API + admin, zero manual edits — verified by a from-scratch run.git cloneof this branch into an empty dir →pnpm install(6s) →pnpm setup:env(3 files, secrets generated) →pnpm dev. Result:@thebrain/types:build: cache miss, executing(a real compile, not a cache replay), thenlistening on http://localhost:4000/apiand adminReady in 394ms.GET /api/health→ 200;POST /api/auth/register→ 201 with a JWT signed by the generated secret;GET localhost:3000→ 200<title>TheBrain — Admin</title>, renders with zero console errors.docker-compose.ymluses${POSTGRES_PORT:-5432}; withPOSTGRES_PORT=5433, all three containers healthy.Compose change landed in chore(infra): make the postgres host port configurable (#315) #316; verified live here —
postgres: healthy (5432→5433),redis: healthy,nats: healthy. This PR adds thePOSTGRES_PORTdocumentation the examples were missing..nvmrcpins 22 andengine-strict=true; Node 20 fails immediately naming the required version.Under v20.19.4:
ERR_PNPM_UNSUPPORTED_ENGINE … Expected version: >=22 Got: v20.19.4, exit code 1.pnpm devsucceeds withpackages/*/distdeleted (noERR_MODULE_NOT_FOUND).Proven in both directions. Fix removed → reproduces the issue's error verbatim:
Cannot find module '.../@thebrain/types/dist/index.js' imported from .../encode-queue.ts. Fix in place →types#build/core#buildrun first, API boots,grep -c ERR_MODULE_NOT_FOUND= 0. Task graph confirms@thebrain/api#dev ← @thebrain/types#build, @thebrain/core#build./opsRuntime Config and Feature Flags render instead of "Insufficient role".pnpm seed→role: user, thenpnpm promote -- demo@thebrain.dev superadmin→user → superadmin(confirmed in Postgres). As superadmin:GET /api/admin/config200,GET /api/admin/flags200 (real entries). Demoted the same user toadmin: both 403 — the "Insufficient role" the AC names. Re-running promote is idempotent; bad role, unknown user, no-args, andNODE_ENV=productionare all rejected with actionable messages.pnpm androidsucceeds with only JDK 17 + Android SDK.README written (first half done). The build itself is unverified — this machine has no JDK and no Android SDK (
/usr/libexec/java_home -V→ "Unable to locate a Java Runtime"), sopnpm androidcould not be run at all. The gradle.properties change is reasoned from the RN/Gradle sources quoted above, not from a green build. Flagging rather than ticking; needs a run on a machine with the Android toolchain.CLAUDE.mdgains a short "fresh machine" section.Added, and mirrored verbatim into
AGENTS.md(the Codex twin) so the two don't drift.Verification summary
New tests — 16 of the 989, TDD red→green (both files failed with
Cannot find modulebefore the implementations existed):apps/api/src/dev/promote.test.ts— the argument contract and production guard.Includes a regression test for the
--separator:pnpm … promote -- a@b.c adminforwards
--intoargv, so the documented invocation failed until handled.Caught by running the command rather than by reading it.
apps/api/src/dev/seed-credentials.test.ts— asserts the demo credentials satisfyregisterRequestSchema/loginRequestSchema, so finding 7 cannot regress silently,plus
SEED_ROLEparsing (defaults touser, rejects typos before any write).Proven by tests vs. by running (per M11): findings 6 and 7 have real unit tests
because they're logic. Findings 1, 3, 4 and the AC1 cold start are config/tooling —
verified by running them and capturing the output above, including a deliberate
revert of the turbo change to confirm the fix is load-bearing. Finding 5 is
documented and reasoned but not verified.
Invariant check
promotereusesadminRepository.updateRole+authRepository.findByEmail. No raw SQL — removing a raw-SQL workaround is the point of finding 6.setup:envuses onlynode:crypto/node:fs).SEED_ROLEdeclared inturbo.jsonglobalEnv(lint caught the omission).NODE_ENV=production.Closes #315
Co-authored-by: Claude noreply@anthropic.com