v0.26.0 feat: GBrain — MCP Keys OAuth 2.1 + HTTP server + admin dashboard#358
Merged
v0.26.0 feat: GBrain — MCP Keys OAuth 2.1 + HTTP server + admin dashboard#358
Conversation
Add oauth_clients, oauth_tokens, oauth_codes tables to both PGLite and Postgres schemas. Migration v5 creates tables for existing databases. PGLite now includes auth infrastructure (access_tokens, mcp_request_log, OAuth tables) because `serve --http` makes it network-accessible. Extract hashToken() and generateToken() to src/core/utils.ts for DRY reuse across auth.ts and oauth-provider.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements OAuthServerProvider backed by raw SQL (PGLite or Postgres). Supports client credentials, authorization code with PKCE, token refresh with rotation, revocation, and legacy access_tokens fallback. Key decisions from eng review: - Uses raw SQL connection, not BrainEngine (OAuth is infrastructure) - All tokens/secrets SHA-256 hashed before storage - Legacy tokens grandfathered as read+write+admin - sweepExpiredTokens() wrapped in try/catch (non-blocking startup) - Client credentials: no refresh token per RFC 6749 4.4.3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add AuthInfo, scope ('read'|'write'|'admin'), and localOnly fields to
Operation interface. Per-operation audit:
- 14 read ops, 9 write ops, 2 admin ops, 4 admin+localOnly ops
- sync_brain, file_upload, file_list, file_url: admin + localOnly
- Scope enforcement happens in serve-http.ts before handler dispatch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gbrain serve --http starts Express 5 server with: - MCP SDK mcpAuthRouter (authorize, token, register, revoke endpoints) - Custom client_credentials handler (SDK doesn't support CC grant) - Bearer auth + scope enforcement on /mcp tool calls - Admin dashboard auth via HTTP-only cookie + bootstrap token - SSE live activity feed at /admin/events - DCR default OFF (--enable-dcr to enable) - Rate limiting on /token (50/15min) - localOnly operations excluded from HTTP CLI: gbrain serve --http [--port 3131] [--token-ttl 3600] [--enable-dcr] Dependencies: express@5.2.1, express-rate-limit@7.5.1, cors@2.8.6 SDK pinned to exact 1.29.0 (was ^1.0.0) 27 new tests covering OAuth provider, scope enforcement, auth code flow, refresh rotation, token revocation, legacy fallback, and sweep. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin SPA at /admin with client-side routing (#login, #dashboard, #agents, #log). Built with Vite + React, served from admin/dist/. Screens: - Login: one field, one button, zero happy talk - Dashboard: metrics bar, SSE live activity feed, token health panel - Agents: table with scopes/badges, + Register Agent button - Register: modal form (name, scopes), 3 mindless choices - Credentials: full-screen modal, copy buttons, download JSON, warning - Request Log: paginated table (50/page), time-relative timestamps - Agent Detail: slide-out drawer, config export tabs (Perplexity/Claude/JSON) Design tokens: #0a0a0f bg, Inter + JetBrains Mono, 4-32px spacing. Build: bun run build:admin (Vite, 65KB gzipped). Admin API: /admin/api/register-client endpoint for dashboard registration. SPA serving: Express static + index.html fallback for client-side routing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolved conflicts: - .gitignore: kept both admin/dist + admin/node_modules and .idea - src/core/utils.ts: merged randomBytes import with PageInput type import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolved conflicts in: - package.json: kept express/cors/rate-limit + MCP SDK pin to 1.29.0 - bun.lock: regenerated via bun install - src/core/operations.ts: AuthInfo coexists with master's remote/jobId/subagentId/cliOpts fields - src/core/migrate.ts: OAuth migration renumbered from v5 to v24 (master added v5-v16) - src/core/pglite-schema.ts: OAuth tables appended after master's Minion/subagent/cycle_lock tables Also annotated 11 new Minion operations (submit_job, get_job, list_jobs, cancel_job, retry_job, get_job_progress, pause_job, resume_job, replay_job, send_job_message, find_orphans) with appropriate scope — all admin except find_orphans (read). Added @types/express and @types/cors devDependencies for typecheck. Fixed requireBearerAuth option name (provider → verifier). Added explicit Request/Response types to admin handlers. 27/27 OAuth tests pass. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Milestone release: multi-agent GBrain with OAuth 2.1, HTTP server, and React admin dashboard. See CHANGELOG.md for details. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sync README, CLAUDE.md, and docs/mcp/ with the OAuth 2.1 + HTTP server + admin dashboard surface that shipped in v1.0.0.0. - README.md: new "Remote MCP with OAuth 2.1" section covering gbrain serve --http, admin dashboard, scoped operations, legacy bearer fallback; add serve --http + auth notes to the commands reference. - CLAUDE.md: add src/commands/serve-http.ts, src/core/oauth-provider.ts, admin/ directory as key files; document scope + localOnly additions to Operation contract; add oauth.test.ts (27 cases) to the test list; add v1.0.0 key-commands section clarifying that OAuth client registration is via the /admin dashboard or SDK (no CLI subcommand). - docs/mcp/DEPLOY.md: promote --http as the recommended remote path, add OAuth 2.1 Setup section, list ChatGPT in supported clients, remove the "not yet implemented" footer. - docs/mcp/CHATGPT.md (new): unblocks the P0 TODO. Full ChatGPT connector setup via OAuth 2.1 + PKCE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously auth.ts was a standalone script invoked via
`bun run src/commands/auth.ts`. CHANGELOG and README documented
`gbrain auth ...` commands that didn't actually work.
- Export `runAuth(args)` from auth.ts (keeps standalone entry intact
via `import.meta.url === file://${process.argv[1]}` check)
- Add `auth` to CLI_ONLY + dispatch in handleCliOnly
- New subcommand `gbrain auth register-client <name> [--grant-types]
[--scopes]` wraps GBrainOAuthProvider.registerClientManual
- Lazy DB check: only subcommands that need DATABASE_URL error out
Now the documented CLI flow works end to end:
gbrain auth register-client perplexity --grant-types client_credentials --scopes "read write"
gbrain serve --http --port 3131
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After /ship, the doc subagent wrote docs assuming `gbrain auth register-client` did not exist (it said so explicitly in CLAUDE.md:184). A follow-up commit (c4a86ce) wired it into src/cli.ts + src/commands/auth.ts. These docs were now contradicting reality. - CLAUDE.md: removed "There is no gbrain auth register-client CLI subcommand" claim, documented the three registration paths (CLI / dashboard / SDK). - README.md: replaced `bun run src/commands/auth.ts` hint with `gbrain auth create|list|revoke|test` and `gbrain auth register-client`. - docs/mcp/DEPLOY.md: added CLI registration example above the programmatic example. - TODOS.md: moved "ChatGPT MCP support (OAuth 2.1)" P0 item to Completed with v1.0.0.0 completion note. Closes the P0 that had been blocking the "every AI client" promise since v0.6. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolved conflicts: - VERSION / package.json: kept 1.0.0.0 (our milestone release supersedes master's 0.18.1 point release) - CHANGELOG.md: kept both entries; v1.0.0.0 stays on top, v0.18.1 RLS hardening preserved below - src/core/migrate.ts: master took v24 for rls_backfill_missing_tables (OAuth migration renumbered v24 → v25) Also pulls in master's v0.18.1 RLS hardening: widened doctor RLS check, severity upgrade (warn → fail), GBRAIN:RLS_EXEMPT escape hatch, PGLite skip, rls-and-you.md guide. All of that layers cleanly under our v1.0.0 work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolved conflicts: - VERSION / package.json: kept 1.0.0.0 (our milestone supersedes master's 0.18.2 point release) - CHANGELOG.md: v1.0.0.0 on top, v0.18.2 migration-hardening preserved below (with its own "To take advantage" steps) - src/core/migrate.ts: auto-merged cleanly. Master's v0.18.2 refactored v21/v23 handlers in place; OAuth stays at v25. Picks up v0.18.2's migration-hardening work: atomic v23 (no more v21→v23 integrity window), gbrain doctor --locks for idle-in-tx blockers, reserved-connection primitive for CREATE INDEX CONCURRENTLY, 4-part 57014 diagnostic. All layers cleanly under our v1.0.0 OAuth work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI Tier 1 (Mechanical) was failing on 4 E2E tests after the v0.18.1 RLS hardening landed on master (PR #343). Our v25 oauth_infrastructure migration adds 3 new public tables (oauth_clients, oauth_tokens, oauth_codes) but didn't enable RLS, so gbrain doctor's new check flagged them and the "RLS on every public table" assertion failed. Fixes: - src/schema.sql: ALTER TABLE ... ENABLE ROW LEVEL SECURITY for the 3 OAuth tables inside the existing BYPASSRLS-gated DO block (fresh installs). - src/core/migrate.ts v25: append a BYPASSRLS-gated DO block after the OAuth CREATE TABLE statements (existing installs on upgrade). Mirrors the v24 rls_backfill gating pattern — RAISE WARNING if the current role lacks BYPASSRLS, so migrations don't silently lock the operator out. - src/core/schema-embedded.ts: regenerated via `bun run build:schema`. - test/e2e/mechanical.test.ts: one unrelated v24 test asserted the post- migration version equals exactly '24'. That breaks when any later migration exists (like our v25). Relaxed to `>= 24` since the test's intent is "v24 didn't abort the chain", not "v24 is the final version". Verified locally: 78/78 E2E tests pass against real Postgres 16 + pgvector. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI test/build-llms.test.ts > committed llms.txt + llms-full.txt match current generator output failed. The committed llms-full.txt was built before the v1.0.0 doc updates landed (OAuth 2.1 README section, new docs/mcp/CHATGPT.md, CLAUDE.md serve-http references, etc.), so the regen-drift guard flagged it. Ran `bun run build:llms`. llms.txt is unchanged (skinny index still matches); llms-full.txt picks up 166 net-new lines of bundled content. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d RESOLVER) (#372) * feat(mounts): connected-gbrains PR 0 foundation — registry + resolver + CLI Lays the foundation for connected gbrains (v0.19.0) per the approved plan. This is PR 0 — minimal runtime for direct-transport, path-mounted brains. What this slice ships: - src/core/brain-registry.ts — keyed BrainRegistry with lazy engine init, schema-validated mounts.json loader, DuplicateMountPathError (load-bearing identity check per Codex finding #9 correction), UnknownBrainError with actionable available-id list. Pure: no AsyncLocalStorage, no singleton mutation. ~280 LOC. - src/core/brain-resolver.ts — 6-tier brain-id resolution mirroring v0.18.0's source-resolver.ts so agents learn ONE mental model: 1. --brain <id> 2. GBRAIN_BRAIN_ID env 3. .gbrain-mount dotfile 4. longest-path match over registered mounts 5. (reserved v2 default) 6. 'host' fallback Orthogonal to --source: --brain picks which DB, --source picks the repo within that DB. Corruption-resistant: mounts.json load failures fall through to 'host' instead of breaking every CLI invocation. - src/commands/mounts.ts — `gbrain mounts add|list|remove` (direct transport only). Validates on add (path exists on disk, id regex, no dupes). WARNS but does not block on same db_url/db_path across ids (teams may legitimately alias a remote brain). Password redaction in list output. Atomic write via temp+rename. 0600 perms. PR 1 adds pin/sync/enable; PR 2 adds --mcp-url + OAuth. - src/cli.ts — wires `gbrain mounts` into handleCliOnly (no DB required for the config-only subcommands). - test/brain-registry.test.ts (28 cases): schema validation across every malformed-input branch, ALS-free resolution, duplicate id + path detection, disabled-mount exclusion, UnknownBrainError context. - test/brain-resolver.test.ts (22 cases): priority order (explicit > env > dotfile > path-prefix > fallback), dotfile walk-up, malformed dotfile recovery, longest-prefix match, sibling-path false-positive guard, loader-failure defense. - test/mounts-cli.test.ts (17 cases): parseAddArgs surface, redactUrl, atomic write, add/list/remove roundtrip via temp HOME. 67 new tests, all green. Typecheck clean. Depends on mcp-key-mgmt (base branch) for the OAuth/scope annotations that PR 2 will leverage. Next in this branch: PR 0 still needs (a) the deep host-brain-bias audit (postgres-engine internal singleton fallback + a few operations.ts callers), (b) OperationContext threading to make ctx.brainId populated at dispatch, (c) composeResolvers + composeManifests, (d) aggregated ~/.gbrain/mounts-cache/ for host-agent runtime ownership. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(mounts): brains-and-sources mental model + agent routing convention Two orthogonal axes organize GBrain knowledge. Users AND agents need to understand both, or queries misroute silently. --brain → WHICH DATABASE (host + mounts) --source → WHICH REPO IN DB (v0.18.0 sources: wiki, gstack, ...) Both axes use the same 6-tier resolution (explicit > env > dotfile > path-prefix > default > fallback), so learning one teaches both. Ships: - docs/architecture/brains-and-sources.md — canonical mental model doc. Covers four topologies with ASCII diagrams: 1. Single-person developer (one brain, one source) 2. Personal brain with multiple repos (one brain, N sources) 3. Personal + one team brain mount (2 brains) 4. Senior user with multiple team memberships (N mounted team brains alongside personal) — the CEO-class topology Explicit "when to move each axis" decision table. Generic example names throughout per the project's privacy rule. - skills/conventions/brain-routing.md — agent-facing decision table. Rules for when to switch brain (team-owned question, explicit name, data owner changes) vs switch source (working in a repo, topic scoped to one repo). Cross-brain federation is latent-space only in v0.19 — the agent fans out; the DB never does. Anti-patterns listed: silent brain jumps, writing to host when data is team-owned, missing brain prefix in citations, ignoring .gbrain-mount dotfiles. - CLAUDE.md — adds "Two organizational axes (read this first)" section at the top pointing at both new docs. - AGENTS.md — adds brains-and-sources.md + brain-routing.md to the "read this order" (positions 3 and 4, before RESOLVER.md). - skills/RESOLVER.md — adds brain-routing.md to the Conventions section so it appears alongside quality.md, brain-first.md, subagent-routing.md. No code changes. Pre-existing check-resolvable warnings unchanged (2 warnings on base unrelated to this work). 67 PR-0 tests still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mounts): thread brainId through OperationContext + subagent chain PR 0 plumbing for connected gbrains. Adds an optional brainId field that identifies which database an operation targets and ensures subagents inherit the parent job's brain instead of process-wide defaults. No dispatch-path changes in this commit — that is PR 1 (registry wiring at MCP + CLI entry points). The fields exist so callers can set them now and downstream code respects them. Changes: - src/core/operations.ts: OperationContext grows `brainId?: string`. Optional for back-compat. 'host' is the implicit default when absent. Orthogonal to v0.18.0's source_id (source = which repo within the brain, brain = which database). See docs/architecture/brains-and-sources.md. - src/core/minions/types.ts: SubagentHandlerData gains `brain_id?: string`. Parent jobs set this when submitting a child subagent to lock the child into a specific brain. Omitted = host (unchanged behavior). - src/core/minions/handlers/subagent.ts: buildBrainTools call site reads data.brain_id and passes it through. Child subagents spawned from this handler will see the same brainId unless they override in their own data. - src/core/minions/tools/brain-allowlist.ts: BuildBrainToolsOpts + OpContextDeps grow brainId; buildOpContext stamps it on every OperationContext the subagent builds for tool calls. Addresses Codex finding #6 (brain-allowlist hardwired parent config without brain awareness, so switching brain only in subagent.ts was not enough). Tests: 166 affected tests green (subagent suite + minions + brain registry + resolver). Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mounts): composeResolvers + composeManifests + aggregated cache The runtime ownership seam for connected gbrains (Codex finding #3 from plan review): check-resolvable.ts VALIDATES RESOLVER.md; it does not DISPATCH skills. Host agents (Wintermute/OpenClaw/Claude Code) read skills/RESOLVER.md directly to route user requests. Without an aggregated resolver, mounted team brains cannot contribute skills to the host agent's routing table. This commit adds the aggregation: - src/core/mounts-cache.ts (NEW): pure composeResolvers + composeManifests functions plus filesystem writers for ~/.gbrain/mounts-cache/. The aggregated files carry every host skill plus every mount skill, namespace-prefixed (e.g. `yc-media::ingest`). Host skills always beat a same-named mount skill (locked decision 1); bare-name collisions between two mounts surface as structured ambiguity info so doctor can warn (PR 1). Also addresses Codex finding #8: manifests compose alongside the resolver, else doctor conformance breaks on remote skills. - src/commands/mounts.ts: refreshMountsCache() called on `mounts add` and `mounts remove` (the latter clearing the cache entirely when the last mount goes away). Uses findRepoRoot() to locate the host skills dir; skips with a stderr note when run outside a gbrain repo so the user isn't confused by a "cache not refreshed" error in the wrong cwd. - test/mounts-cache.test.ts (NEW): 23 unit tests covering empty world, host-only, single mount, two-mount ambiguity, host-shadows-mount, disabled mount excluded, missing RESOLVER.md is a no-op, manifest composition with same-name collision, render shape, atomic rewrite, clear on missing dir. Output format for ~/.gbrain/mounts-cache/RESOLVER.md adds a Brain column so host agents can see which brain each trigger routes to at a glance, plus Shadows and Ambiguous sections when those conditions exist. Tests: 90 PR 0 tests green (brain-registry + resolver + mounts-cache + mounts-cli). Full suite regression pending in task 11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mounts): force instance-level pool for mount brains + CI guard Closes the silent-singleton-share bug Codex flagged as finding #1 from the plan review: two direct-transport mounts with different Postgres URLs would both fall through postgres-engine.ts's `get sql()` getter to db.getConnection() and quietly share whichever singleton connected first. Your yc-media writes end up in garrys-list or vice versa. No error at the call site — just wrong data. The fix: - src/core/brain-registry.ts: initMountBrain now passes poolSize when calling engine.connect(). That forces postgres-engine.ts:33-60 down the instance-level path (setting this._sql) instead of the module singleton path (calling db.connect). Hard-coded 5 for PR 0 — per-mount override is PR 1. PGLite ignores poolSize (no pool concept), so this is Postgres-specific. Host brain still uses the singleton path via initHostBrain (unchanged). That is fine for PR 0: the singleton is "the host's one connection" by definition. PR 1 removes the singleton entirely once every CLI command is engine-injectable. - scripts/check-no-legacy-getconnection.sh (NEW): CI grep guard against new db.getConnection() / db.connect() calls landing in src/core/ or src/commands/ (the multi-brain dispatch surface). Has an explicit ALLOWED list grandfathering today's legitimate callers, each marked "PR 1 refactors" so the list shrinks over time. Skips comment lines so the grep doesn't trip on doc references to the old pattern. - package.json: scripts.test chains the new guard after the existing check-jsonb-pattern + check-progress-to-stdout guards. `bun run test` now fails the build on singleton regression. Tests: 295 affected pass (registry, resolver, mounts-cache, mounts-cli, minions, pglite-engine). Typecheck clean. CI guard reports "ok: no new singleton callers" on current tree. Left for PR 1: remove the singleton fallback in postgres-engine.ts's `get sql()` entirely; refactor src/commands/doctor.ts, files.ts, repair-jsonb.ts, serve-http.ts, init.ts, and the 3 localOnly ops in operations.ts (file_list, file_upload, file_url) to accept ctx.engine explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mounts): codex review findings — namespace survives shadow + atomic tmp names + honest PR 0 docstrings Codex outside-voice review on PR #372 found 5 issues. Real bugs fixed, overclaims rewritten. Details: P2 (real bug): composeResolvers and composeManifests were silently dropping mount entries when a host skill shared the short name, which made the namespace-qualified form `<mount>::<skill>` unreachable once host defined the same short name. That defeated the entire namespace-disambiguation model — if host had `ingest`, no mount could ship an `ingest` skill even with explicit `yc-media::ingest`. Fix: always keep namespace-qualified mount entries in the composed output. Shadow tracking moves to metadata (`shadows[]`) that doctor can warn on, but never drops routing. Before: host ingest + yc-media ingest → only 1 entry (host), yc-media::ingest unreachable After: host ingest + yc-media ingest → 2 entries: bare `ingest` = host, `yc-media::ingest` = mount Verified live: gbrain mounts add of a mount with `ingest` now shows `team-demo::ingest` alongside host `ingest` in the aggregated manifest. P1 (real bug): writeMountsFile + writeMountsCache used fixed `.tmp` filenames. Two concurrent `gbrain mounts add` invocations (e.g. from parallel terminals or CI) would clobber each other's temp file and one writer's update would be lost. Fix: tmp filenames include `process.pid + random suffix` so every writer has its own scratch file. The atomic rename is self-contained per-writer. (Full lock + read-modify- write safety deferred to PR 1 under `gbrain mounts sync --lock`.) P1 (honesty): `SubagentHandlerData.brain_id` + `BuildBrainToolsOpts.brainId` docstrings claimed child jobs inherit the parent's brain and brain tools target the resolved brain. True for the `ctx.brainId` field only — `ctx.engine` is still the worker's base engine at dispatch time because `buildOpContext` doesn't yet do the registry lookup, and `gbrain agent run` doesn't yet accept `--brain` to populate the field on submission. Rewrote both docstrings to state the PR 0 behavior explicitly (field plumbed, engine routing is PR 1) so nobody reads the code thinking multi-brain subagents already work. Also cleaned up two `require('fs')` runtime imports left over from the initial PR — swapped for ESM named imports (renameSync). Pre-existing style issue surfaced by the self-review pass. Tests: 90 PR-0 tests pass. Updated two shadow-related test cases to assert the corrected semantics (both entries survive, host wins bare name, namespace form routes to mount). Not fixed in this commit (documented as known PR 0 limitations): - `file_list` / `file_upload` / `file_url` in operations.ts still hit the singleton (localOnly + admin, never reachable from HTTP MCP — safe in practice, refactor in PR 1 alongside command-level cleanups). - writeMountsCache's two-file swap (RESOLVER.md + manifest.json) is not atomic across files; readers can briefly observe mismatched pairs. Acceptable because the cache is recomputable at any time from mounts.json. Generation-directory swap is PR 1 work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tests): bump hook timeouts for 21-migration PGLite init under full-suite load Root cause of 19 pre-existing full-suite flakes (CHANGELOG v0.18.0 noted "17 pre-existing master timeouts"): every PGLite test does beforeAll/beforeEach(async () => { engine = new PGLiteEngine(); await engine.connect({}); await engine.initSchema(); // runs 21 migrations through v0.18.2 }); In isolation this takes ~5s. Under full-suite contention (128 files, process-shared FS and CPU) it exceeds bun's default 5000ms hook timeout, beforeEach times out, engine stays undefined, then afterEach crashes with `TypeError: undefined is not an object (evaluating 'engine.disconnect')`. That single hook failure reports as the whole test "failing" even though the test body never executed, which is why the failure count sometimes looked inflated compared to the number of genuinely-broken tests. Fix applied across 7 test files: - Raise setup hook timeout to 30_000 (6x the default) — gives migration init enough headroom even under worst-case load without masking real regressions in a post-migration test. - Raise teardown hook timeout to 15_000 — engine.disconnect() is usually fast but can stall when PGLite's WASM runtime is still completing a migration at shutdown. - Add `if (engine) await engine.disconnect()` guard so afterEach doesn't double-fault when beforeEach already failed. This was the source of the opaque "(unnamed)" failures — they were disconnect crashes, not test-body failures. Files: test/dream.test.ts (5 beforeEach + 5 afterEach blocks) test/orphans.test.ts (1 pair) test/brain-allowlist.test.ts (1 pair) test/oauth.test.ts (1 pair) test/extract-db.test.ts (1 pair) test/multi-source-integration.test.ts (1 pair) test/core/cycle.test.ts (1 pair) Results on the merged PR 0 branch: Before: 2175 pass / 20 fail / 3 errors After: 2281 pass / 0 fail / 0 errors (+106 tests running that were previously blocked by the timed-out hooks) No changes to production code. No test assertions changed. Just timeout-bump + null-guard discipline that should have been in these hooks from the start. The real longer-term fix is reusing an engine across tests where possible (brain-allowlist.test.ts already does this via beforeAll+DELETE-pages pattern), but that's per-file structural work — out of scope for this cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: regenerate llms-full.txt for brains-and-sources + brain-routing docs The test/build-llms.test.ts test validates that the committed llms.txt and llms-full.txt match the current generator output. PR 0 added docs/architecture/brains-and-sources.md content paths and updated CLAUDE.md + skills/RESOLVER.md in earlier commits, but the generated bundle file wasn't regenerated alongside. This caused one of the 20 fails we chased down today — a straight content mismatch, not a runtime bug. Running `bun run build:llms` picks up the new section content so the bundle matches the sources again. No functional change. Only the compiled doc bundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.19.0 (skillify + skillpack + routing-eval + check-resolvable AGENTS.md support) onto the v1.0.0.0 OAuth + admin dashboard branch. Conflicts resolved: - VERSION: kept 1.0.0.0 (our major release supersedes 0.19.0). - package.json: kept 1.0.0.0 version. - src/cli.ts CLI_ONLY set: merged both — kept mounts/auth/dream/ check-resolvable (ours) and added skillpack/routing-eval/skillify (master). Kept mounts command dispatch and added the three new master commands alongside. - CHANGELOG.md: stacked 1.0.0.0 entry above the preserved 0.19.0 entry. v0.18.2 and earlier entries untouched. Typecheck clean. 108 tests across cli/oauth/brain-registry/brain-resolver/ mounts-cli pass locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.19.1 (smoke-test CLI + Zod CJS auto-repair + RESOLVER wiring) onto the v1.0.0.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 1.0.0.0 (supersedes 0.19.1). - src/cli.ts CLI_ONLY set: merged — kept mounts/auth (ours) and added smoke-test (master). smoke-test dispatch block came through cleanly. - CHANGELOG.md: stacked 1.0.0.0 above preserved 0.19.1 above 0.19.0. Typecheck clean. cli + oauth tests pass locally (41 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.20.0 (BrainBench extracted to sibling gbrain-evals repo, 11 new
public subpath exports) onto the v1.0.0.0 OAuth branch.
Conflicts resolved:
- VERSION / package.json: kept 1.0.0.0 (supersedes 0.20.0).
- src/cli.ts: auto-merged cleanly (no CLI_ONLY diff this round).
- CHANGELOG.md: stacked 1.0.0.0 above preserved 0.20.0 above 0.19.1.
- CLAUDE.md: merged test-file inventory — kept oauth.test.ts entry and
appended master's v0.19 skill test files.
- llms-full.txt: regenerated via `bun run build:llms` after CLAUDE.md fix.
- test/{brain-allowlist,core/cycle,extract-db,multi-source-integration,
orphans,dream}.test.ts: settled on 60_000 timeouts with defensive
`if (engine)` check. OAuth v25 + full migration chain needs the
breathing room that master's 60s values already provide.
Typecheck clean. cli + oauth tests pass (41 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.20.2 (gbrain jobs supervisor — self-healing worker process manager with atomic PID lock + JSONL audit + three-command agent API) onto the v1.0.0.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 1.0.0.0 (supersedes 0.20.2). - CHANGELOG.md: stacked 1.0.0.0 above preserved 0.20.2 above 0.20.0. - llms-full.txt: regenerated via `bun run build:llms`. Typecheck clean. cli + oauth tests pass (41 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.20.3 (queue resilience: wall-clock timeouts, backpressure audit, --no-worker liveness probe, env concurrency clamp, --max-waiting CLI flag, queue_health doctor check) onto the v1.0.0.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 1.0.0.0 (supersedes 0.20.3). - CHANGELOG.md: stacked 1.0.0.0 above preserved 0.20.3 above 0.20.2. - llms-full.txt: regenerated via `bun run build:llms`. - CLAUDE.md and src/core/minions/types.ts auto-merged cleanly. Typecheck clean. cli + oauth tests pass (41 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.20.4 (minion-orchestrator skill consolidation + CLI example accuracy + resolver round-trip tests) onto the v1.0.0.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 1.0.0.0 (supersedes 0.20.4). - CHANGELOG.md: stacked 1.0.0.0 above preserved 0.20.4 above 0.20.3. - llms-full.txt: regenerated via `bun run build:llms`. - CLAUDE.md, README.md, skills/RESOLVER.md auto-merged cleanly. Typecheck clean. cli + oauth tests pass (41 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.21.0 (Cathedral II — call-graph edges, two-pass retrieval, parent-scope chunking) onto the v1.0.0.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 1.0.0.0 (supersedes 0.21.0). - package.json test script: merged both pre-test guards (kept ours check-no-legacy-getconnection.sh, added master's check-wasm-embedded.sh + --timeout=60000). Added master's check:wasm script. Picked up new deps (web-tree-sitter, tree-sitter-wasms) via bun install. - src/cli.ts CLI_ONLY set: merged — kept mounts/auth (ours) and added master's repos/code-def/code-refs/reindex-code/code-callers/code-callees. - src/core/migrate.ts: master's v25-v29 (page_kind / code_metadata / cathedral_ii_foundation / chunk_fts_backfill / code_edges_rls) land in order. Our OAuth migration renumbered v25 → v30 to ride on top — OAuth is independent of the code-graph chain so ordering doesn't matter beyond ledger correctness. Verified by migrate.test.ts (90 tests pass; ledger shows v15 → v30 with all 12 migrations applying cleanly). - test/e2e/mechanical.test.ts: kept master's "advanced PAST 24" comment, noted v30 (OAuth) at the end of the migration chain. - CHANGELOG.md: stacked v1.0.0.0 above master's v0.21.0 + expanded v0.19.0 entries. Removed the slim v0.19.0 duplicate that master's expanded entry supersedes. Order: v1.0.0.0 → v0.21.0 → v0.19.0 (expanded) → v0.20.4 → ... → v0.18.2. - llms-full.txt: regenerated via `bun run build:llms`. Typecheck clean. cli + oauth + migrate tests pass (90 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OAuth + admin dashboard is meaningful but doesn't quite warrant the
major-version reset to 1.0. Renumber as v0.22.0, slotting cleanly above
master's v0.21.0 (Cathedral II).
Touched:
- VERSION, package.json: 1.0.0.0 → 0.22.0
- CHANGELOG.md: heading + "BEFORE/AFTER v1.0" table + "To take advantage"
+ "pre-v1.0" all renamed. Narrative voice unchanged otherwise.
- TODOS.md: ChatGPT MCP completion stamp updated to v0.22.0 (2026-04-25).
- CLAUDE.md, README.md, docs/mcp/{DEPLOY,CHATGPT}.md, src/schema.sql,
src/core/schema-embedded.ts: every reader-facing v1.0.0 reference
rewritten to v0.22.0 / pre-v0.22 in the same place.
- llms-full.txt: regenerated to match.
Slug-test occurrences of "v1.0.0" (`test/slug-validation.test.ts`,
`test/file-upload-security.test.ts`) and the `HOMEBREW_FOR_PERSONAL_AI`
roadmap reference to a future v1.0 vision left intact — those are
unrelated to this branch's release version.
Typecheck clean. cli + oauth + slug + file-upload tests pass (106 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumped 0.22.0 → 0.26.0 to slot above master's v0.21 chain with headroom for v0.23/0.24/0.25 to ship from master between now and merge. Security fixes (all from CSO finding writeups): #1 cookie-parser middleware — admin dashboard auth was silently broken. Express 5 has no built-in cookie parsing; req.cookies was always undefined, so /admin/login set the cookie but every subsequent admin API call returned 401. Added cookie-parser@^1.4.7 + @types/cookie-parser as direct + dev deps. app.use(cookieParser()) wired before CORS. #2 + #3 TOCTOU races — exchangeAuthorizationCode and exchangeRefreshToken used SELECT-then-DELETE, letting concurrent requests with the same code/refresh both pass the SELECT before either ran DELETE, both issuing token pairs. Switched to atomic DELETE...RETURNING. RFC 6749 §10.5 (codes) + §10.4 (refresh detection) violations closed. Added regression tests that fire 10 concurrent exchanges and assert exactly one wins — both pass. #5 pgArray escape + DCR redirect_uri validation — pgArray() did `arr.join(',')` with no escaping, so an element containing a comma would be parsed by Postgres as TWO array elements. With --enable-dcr on, this could smuggle a second redirect_uri into a registered client and steal auth codes. Now every element is double-quoted with `"` and `\` escaped. Added validateRedirectUri() per RFC 6749 §3.1.2.1: redirect_uris must be https:// or loopback (localhost / 127.0.0.1). Wired into the DCR registerClient path; CLI registration trusts the operator and bypasses. Regression test confirms a comma-in-URI element round-trips as 1 element, not 2. #6 --public-url flag — issuerUrl was hardcoded to http://localhost:{port}. Behind reverse proxies / ngrok / production deploys, the issuer claim in tokens wouldn't match the discovery URL clients hit (RFC 8414 §3.3). New --public-url URL flag on `gbrain serve --http`, propagates through serve.ts → serve-http.ts → ServeHttpOptions.publicUrl → issuerUrl. Startup banner surfaces the configured issuer. Findings #4 (admin requests filter dead code), #7 (admin register-client hardcoded grant_types), #8 (legacy token grandfathering posture) are documentation / minor functional fixes and are deferred per user direction. Tests: oauth.test.ts now 34 cases (was 27). 7 new: - single-use TOCTOU regression (10 concurrent code exchanges) - single-use TOCTOU regression (10 concurrent refresh exchanges) - redirect_uri http://localhost passes - redirect_uri https://example.com passes - redirect_uri http://example.com (non-loopback plaintext) rejected - redirect_uri non-URL rejected - redirect_uri with embedded comma stored as single element Files: - VERSION, package.json: 0.22.0 → 0.26.0 - CHANGELOG.md: heading + table + "To take advantage" + "pre-v0.22" → v0.26; new "Security hardening (post-/cso pass)" subsection at top of itemized changes; CLI flag list updated for --public-url. - src/core/oauth-provider.ts: pgArray escape, validateRedirectUri, registerClient enforces validation, DELETE...RETURNING in exchangeAuthorizationCode + exchangeRefreshToken. - src/commands/serve-http.ts: cookie-parser import + wire-up, publicUrl option, issuerUrl honors it, startup banner shows issuer. - src/commands/serve.ts: parses --public-url and threads through. - src/cli.ts: help text adds --public-url URL flag. - test/oauth.test.ts: +7 regression tests (now 34 total). - llms-full.txt: regenerated. Typecheck clean. 34 oauth + 14 cli tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.22.0 → v0.22.9 (multi-PR sync ladder including the v0.22.7 "built-in HTTP transport with bearer auth", v0.22.4 frontmatter-guard, v0.22.5 source-aware search, v0.22.6 PGLite upgrade hardening, etc.) onto the v0.26.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 1.0.0.0 → 0.26.0 (above master's 0.22.9). - src/cli.ts CLI_ONLY: merged — added master's `frontmatter` command, kept ours `mounts`/`auth`. - src/commands/serve.ts: kept our OAuth-aware --http path that dispatches to serve-http.ts. Master's simpler startHttpTransport (v0.22.7) is superseded; legacy access_tokens still authenticate via OAuth provider's verifyAccessToken fallback so v0.22.7 callers keep working. - src/commands/auth.ts: merged — kept master's getDatabaseUrl(requireDb) and runAuth() dispatcher shape, added our register-client subcommand + registerClient function. Removed a duplicate runAuth block that the auto-merge left behind. - CHANGELOG.md: stacked v0.26.0 above master's full v0.22.x chain (v0.22.9 → v0.22.0). Order: v0.26.0 → v0.22.9 → v0.22.8 → ... → v0.22.0 → v0.21.0. - CLAUDE.md: merged — kept master's notes on dispatch.ts + rate-limit.ts (real new files, useful infrastructure), updated serve-http.ts entry to note it supersedes the v0.22.7 simpler path. - README.md: merged --http examples + flag list. Updated the headline example to include --public-url for tunnels. - docs/mcp/DEPLOY.md: kept the OAuth path as primary, updated note to surface the v0.22.7 fallback compatibility. - llms-full.txt: regenerated. Typecheck clean. cli + oauth tests pass (48 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.23.0 (dream verdicts) + v0.23.1/v0.23.2 (CI gate, orchestrator self-consumption) + v0.24.0 (skillify production hardening) + v0.25.0 (BrainBench-Real session capture) onto the v0.26.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 0.26.0 (above master's 0.25.0). - package.json scripts: merged check guards — kept ours check-no-legacy-getconnection.sh, added master's check-privacy.sh + check-trailing-newline.sh + check-exports-count.sh + build:pglite-snapshot. devDeps: kept @types/cookie-parser/cors/express AND added master's bun-types. - src/cli.ts CLI_ONLY: merged — kept ours mounts/auth, added master's storage/friction/claw-test. - src/core/migrate.ts: master's v30 (dream_verdicts) + v31 (eval_capture_tables) land in order. Our OAuth migration renumbered v30 → v32 to ride on top — independent of those chains so ordering doesn't matter beyond ledger correctness. Verified by migrate.test.ts: ledger walks v15 → v32 with all 14 pending migrations applying cleanly. - src/core/minions/handlers/subagent.ts + tools/brain-allowlist.ts + types.ts: merged — kept BOTH our brainId (connected-gbrains plumbing) AND master's allowedSlugPrefixes (v0.23 trusted-workspace allow-list). Independent features; both flow through buildBrainTools and OperationContext. - src/schema.sql + src/core/schema-embedded.ts: RLS block layered v0.23 + v0.25 tables (dream_verdicts, eval_candidates, eval_capture_failures) alongside our v0.26 oauth tables. schema-embedded.ts regenerated via bun run build:schema. - CHANGELOG.md: stacked v0.26.0 above master's full v0.23.x → v0.25.0 chain. Order: v0.26.0 → v0.25.0 → v0.24.0 → v0.23.2 → v0.23.1 → v0.23.0 → v0.22.16 → ... → v0.21.0. - CLAUDE.md: merged operations.ts entry — kept master's v0.23 trusted- workspace + matchesSlugAllowList notes AND our v0.26 scope/localOnly annotations. - llms-full.txt: regenerated. - bun.lock: took theirs and ran `bun install` to add our cookie-parser + cors + express + bun-types deps. Typecheck clean. cli + oauth + migrate tests pass (111 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings v0.25.1 (book-mirror flagship + 8 research skills + skillpack uninstall + post-install advisory) onto the v0.26.0 OAuth branch. Conflicts resolved: - VERSION / package.json: kept 0.26.0 (above master's 0.25.1). - src/cli.ts CLI_ONLY: merged — kept ours mounts/auth, added master's book-mirror. - CHANGELOG.md: stacked v0.26.0 above master's v0.25.1. - llms-full.txt: regenerated. Typecheck clean. cli + oauth tests pass (48 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Summary
GBrain v0.26.0 milestone release. Multi-agent is real: OAuth 2.1 provider, Express HTTP server (
gbrain serve --http), embedded React admin dashboard. Every AI client can connect with scoped tokens.OAuth 2.1 infrastructure
GBrainOAuthProviderimplementing MCP SDK'sOAuthServerProvider+OAuthRegisteredClientsStoreinterfaces, backed by raw SQL (works on PGLite + Postgres).mcpAuthRouter(SDK's token endpoint throws UnsupportedGrantTypeError for CC; we handle it thennext()for other grants).access_tokensfallback grandfathers pre-v0.26 bearer tokens asread+write+admin.HTTP server
gbrain serve --http [--port 3131] [--token-ttl 3600] [--enable-dcr]starts an Express 5 server with the full OAuth surface + MCP endpoint at/mcp.scope: 'read' | 'write' | 'admin'on every operation,localOnly: trueonsync_brain/file_upload/file_list/file_url(rejected over HTTP)./token(50/15min). SSE live activity feed at/admin/eventsbroadcasts every MCP request.--enable-dcr.React admin dashboard
admin/dist/. 65KB gzipped.CLI
gbrain serve --http— new HTTP transport alongside stdiogbrain serve.gbrain auth register-client <name> --grant-types client_credentials --scopes "read write"— OAuth client registration.gbrain auth create/list/revoke/testpreserved.Operation contract changes
Operation.scope?: 'read' | 'write' | 'admin'andOperation.localOnly?: boolean— annotated per-op on all 30 operations plus 11 Minion ops that came in via master merge.OperationContext.auth?: AuthInfothreaded through HTTP dispatch.Schema
oauth_clients,oauth_tokens,oauth_codes. Composite index onmcp_request_log(created_at, token_name)for dashboard time-range queries.oauth_infrastructure) creates everything idempotently.serve --httpmakes it network-accessible).Test Coverage
test/oauth.test.ts— 27 new test cases covering: register, getClient, client_credentials exchange (valid/wrong-secret/no-CC-grant/no-refresh), auth_code flow with PKCE (valid/expired/wrong-client/PKCE-mismatch/code-reuse), refresh rotation, verifyAccessToken (OAuth + legacy fallback), revokeToken, sweepExpiredTokens, scope annotations on all 30 operations.Pre-Landing Review
No issues found in this branch's code. Merge commit cleanly resolved 5 conflict files with upstream v0.17/v0.18 changes (operations.ts OperationContext, migrate.ts migration ordering, pglite-schema.ts table additions, package.json deps, bun.lock regen).
Design Review
Admin dashboard pre-reviewed through
/plan-design-reviewwith Krug's lens. 3/10 → 7/10 completeness. 8 design decisions locked in (hierarchy, design tokens, interaction states, credential-reveal UX, pagination over infinite-scroll, responsive breakpoints, a11y, drawer pattern). 7 mockup screens approved.Plan Completion
TODOS
Documentation
gbrain serve --http, the admin dashboard, scoped operations, and the legacy bearer fallback. Commands reference listsserve --http,gbrain auth create|list|revoke|test, andgbrain auth register-client.src/commands/serve-http.ts,src/core/oauth-provider.ts, andadmin/to key files; documentedscope+localOnlyadditions to theOperationcontract; addedtest/oauth.test.ts(27 cases) to the test catalog. Corrected: three OAuth client registration paths documented (CLI / dashboard / SDK) after thegbrain auth register-clientsubcommand was wired.--httpas the recommended remote path, added OAuth 2.1 Setup section with CLI + dashboard + SDK registration examples, listed ChatGPT in supported clients.beforeAllhook timeouts under parallel test runner (18 pre-existing upstream flakes).Test plan
gbrain authhelp rendersgbrain serve --http, connect Perplexity, watch admin feed🤖 Generated with Claude Code
Post-/cso security fixes (added in this round)
After running
/cso --diffon the OAuth + HTTP + admin surface, four findings landed in this branch:cookie-parsermiddleware missing — admin dashboard auth was silently broken in v0.22's original shipcookie-parser@^1.4.7direct dep +app.use(cookieParser())DELETE...RETURNINGDELETE...RETURNINGpgArray()no-escape join allowed comma-element smuggling; DCR redirect_uri lacked HTTPS gatevalidateRedirectUri()enforces https:// or loopback per RFC 6749 §3.1.2.1issuerUrlhardcoded tohttp://localhostbroke discovery metadata behind reverse proxy / ngrok / production--public-url URLflag ongbrain serve --http, propagates to OAuth issuerFindings #4, #7, #8 are documentation / minor functional fixes and are deferred to a follow-up.
Regression tests added (7 new, oauth.test.ts now 34 cases):
CSO findings report:
.gstack/security-reports/2026-04-25-cso-v0.22.0.json(gitignored; preserved locally).