chore(infra): make the postgres host port configurable (#315) - #316
Merged
Conversation
The compose file hardcoded `5432:5432`, so `pnpm infra:up` dies with
"Bind for 0.0.0.0:5432 failed: port is already allocated" on any machine
already running another local Postgres — and it fails HALF-WAY: redis and
nats come up, postgres does not, leaving a confusing partial stack.
Bind the host side to `${POSTGRES_PORT:-5432}` instead. The default is
unchanged, so nothing moves for anyone not setting the variable; a dev with
a port conflict sets `POSTGRES_PORT=5433` in the root `.env` and points
`apps/api/.env` DATABASE_URL at the same port. The container side stays
5432, so every in-network service URL is untouched.
Refs #315 (finding 2 of 7).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jayesh-keychain
added a commit
that referenced
this pull request
Aug 1, 2026
…ry point (#320) (#328) * feat(infra): self-hosted Langfuse v3 stack behind an opt-in profile (#320) Stage 1 of #320: the infrastructure only. No application code is instrumented yet — tracing the model router (#320 stage 2) and the recall pipeline (stage 3) land separately, so this PR is reviewable on its own and changes NO runtime behaviour. Langfuse v3 is not one container. It needs ClickHouse for analytics, Redis for queueing, S3-compatible blob storage for event payloads, and separate web and worker processes. That is far too much to impose on every `pnpm infra:up`, so it all sits behind a `langfuse` compose profile — the same opt-in pattern `metabase` already uses. Verified: `docker compose config --services` still reports exactly `nats postgres redis`, and the Langfuse services appear only with `--profile langfuse`. - `langfuse-db-init` creates a dedicated `langfuse` database on the EXISTING Postgres, mirroring `metabase-db-init` (idempotent, safe on every up) rather than adding a second Postgres. - Redis is reused, not duplicated. NOTE: this makes CLAUDE.md §1's "Redis, NATS and MinIO are reserved, not used" no longer true for Redis — the manual needs updating; flagged in #320. - MinIO is added. It was documented as a reserved dependency but was never actually in this file; this is its first real use. - Host ports: 3000 is admin and 3001 is metabase, so Langfuse takes 3002. Every port is `${VAR:-default}` so it can be moved on a machine that already has something bound — the same lesson as #315/#316. - `TELEMETRY_ENABLED=false`: this is a privacy tool, so it must not itself phone home. SELF-HOSTED ONLY, and that is the point rather than a preference: traces carry prompts and completions, which carry memory text. Langfuse Cloud and LangSmith are rejected because shipping that to a third party violates invariant §2.2. The ADR recording this is still owed (#320 stage 1). The three Langfuse secrets (NEXTAUTH_SECRET, SALT, ENCRYPTION_KEY) are wired as env with placeholder defaults so a local stack boots, and documented in .env.example with the generation command. ENCRYPTION_KEY must be exactly 64 hex chars. No real secret is committed. Verification: `docker compose --profile langfuse config` parses and resolves (the shared YAML anchor for the web/worker env expands correctly), and the default service list is unchanged. The stack has NOT yet been booted end-to-end — the images are a multi-GB pull — so "all services healthy" and the first-run Langfuse migration are UNPROVEN here and must be confirmed with `pnpm langfuse:up` before this is relied on. Refs #320 (stage 1 of 5; does not close it). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(infra): record the Langfuse v3 boot verification (#320) The stage-1 commit stated the stack had NOT been booted end-to-end (the images were a multi-GB pull still in flight) and that "all services healthy" plus the first-run migration were unproven. They have now been run — recording the evidence so the branch does not carry a stale caveat. docker compose --profile langfuse ps clickhouse healthy minio healthy langfuse-worker running langfuse (web) running curl http://localhost:3002 -> 200 curl http://localhost:3002/api/public/health -> 200 langfuse database created on the EXISTING postgres -> present first-run migration -> 72 tables in public `langfuse-db-init` created the database, the worker applied the Postgres and ClickHouse migrations, and the web process serves. The pre-existing containers were NOT disturbed: postgres/redis/nats kept their 3-4h uptimes across the profile coming up, confirming the opt-in profile composes with a running dev stack rather than restarting it. Still stage 1 of 5 — no application code is instrumented, and tracing must not be enabled until the stage-2 privacy gating (loopback-only payloads, unconditional redaction for isPrivate, fail-open) exists. Refs #320. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(platform): trace routed LLM calls to self-hosted Langfuse (#320) Stage 2 of #320. Every call through the model router now emits a Langfuse trace + generation with model, class, vendor, token-derived cost and latency. `complete()` is the only instrumentation point needed: ADR-0002 already makes it the chokepoint every LLM call passes through, so one seam covers ingest and recall without touching a single call site. Three rules govern `platform/observability/langfuse.ts`, in priority order: 1. OFF BY DEFAULT — with no keys the client is undefined and every export is a no-op, so dev/CI/tests neither emit nor need a server (§2.5 parity). 2. PAYLOADS STAY ON-BOX — trace input/output ARE prompts and completions, i.e. memory text. They are sent only to a loopback host, reusing the same `isLoopbackUrl` gate as the Ollama provider rather than re-deriving the rule. A remote host degrades to metadata-only unless an operator sets LANGFUSE_ALLOW_REMOTE_PAYLOADS explicitly, so moving a URL in `.env` can never start exfiltrating content (M9). `isPrivate` content is redacted UNCONDITIONALLY — even on loopback: a trace is another store, and a private memory must not be duplicated into one. 3. FAIL-OPEN — every emit is wrapped; a Langfuse outage, bad key or serialisation error is swallowed and logged, never surfacing to a user. Same contract as `fireCoactivationLog`. Also fixes two stage-1 compose defects that green healthchecks hid. The stack reported every container healthy, the UI served 200 and 72 tables migrated — and could still persist ZERO traces: - The `langfuse` MinIO bucket was never created (Langfuse does not create it), so every event died with "Failed to upload events to blob storage, aborting event processing". Added an idempotent `langfuse-minio-init` one-shot mirroring `langfuse-db-init`; both langfuse services now gate on it. - Redis auth: passing REDIS_HOST/REDIS_PORT made Langfuse send an AUTH command to a password-less Redis ("ERR AUTH called without any password configured"). Switched to REDIS_CONNECTION_STRING. Trace input/output are set on the TRACE ROOT as well as the child generation. Setting only the generation left the UI showing "this trace didn't receive an input or output" while the payload sat one level down — the trace root is what the UI opens on. Verified end-to-end against the running stack (Ollama gemma3:4b): GENERATION model=gemma3:4b class=strong vendor=ollama costUsd=0 latencyMs=1808.5 trace input : "What is Priya allergic to?" + retrieved context output: "peanuts" type-check, lint green; 77 platform tests pass unchanged. NOT done, so the #320 privacy AC stays open: the two tests proving private memory text never reaches the client, and that a non-loopback host degrades to metadata-only. The gating code exists and logged `payloads: full` correctly on loopback, but it is not test-covered — and stage 1 already showed what happens when only the happy path is exercised. Refs #320 (stage 2 of 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(admin): reach Langfuse from the Operations Console (#320) Langfuse had no entry point — an operator had to know it existed and type localhost:3002. It now sits in the /ops nav beside "Manage users" and "Audit trail", where operators already look. - An external <a>, not a next/link: Langfuse is a SEPARATE self-hosted app on its own port, not a route in this Next app. - target=_blank + rel=noopener so the console is not lost on the way out. - The URL is NEXT_PUBLIC_LANGFUSE_URL with a :3002 default (3000 = admin, 3001 = metabase), so a deployment that moves it does not patch a component. - Shown unconditionally. The `langfuse` compose profile is opt-in, so the service is often down; a link that explains itself on hover ("opt-in: pnpm langfuse:up") beats an entry point that silently vanishes. Also corrects the Redis service card, which still read "Reserved — not in use this phase". Langfuse queues on Redis as of stage 1, so that was now false on the very page an operator consults to see what is running. Verified in the running admin as a signed-in operator (the page is client-rendered behind auth, so an unauthenticated fetch only returns the shell and cannot confirm this): the link renders as "Observability ↗" with href http://localhost:3002 and target=_blank, and the Redis card shows the corrected text. type-check and lint green. Refs #320 (part of stage 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Jayesh Bhade <jayeshbhade@Jayeshs-MacBook-Pro.local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 3, 2026
jayesh-keychain
added a commit
that referenced
this pull request
Aug 3, 2026
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.
Binds the postgres host port to
${POSTGRES_PORT:-5432}so the stack can coexist with another local Postgres. Default unchanged; container side stays 5432, so every in-network service URL is untouched.Hit for real:
pnpm infra:updied withBind for 0.0.0.0:5432 failed: port is already allocatedagainst an unrelated project's container, and failed HALF-WAY — redis and nats up, postgres dead.Refs #315 (finding 2 of 7 — deliberately does NOT close it; the other six remain).
pnpm type-check,lint,buildgreenPOSTGRES_PORT=5433 pnpm infra:upwith another Postgres holding 5432: all three containers reported healthy, andDATABASE_URLon 5433 migrated + served the full stack for this session's E2E run.🤖 Generated with Claude Code