From 8c956fa2a8710416bd365e2be9cb1ac68011df5a Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 18 May 2026 20:36:12 -0700 Subject: [PATCH] test(gbrain-doctor): pin schema_version:2 doctor parse path (#1418) Adds an exec-path regression test that runs a fake gbrain shim emitting the v0.25+ doctor JSON shape (schema_version: 2, status: "warnings", exit 1 for health_score < 100, no top-level `engine` field). Confirms freshDetectEngineTier recovers stdout from the non-zero exit and falls back to GBRAIN_HOME/config.json for the engine label. The pre-existing test for #1415 only stripped gbrain from PATH; this test exercises the actual doctor parse path, closing the gap that codex's plan review flagged. Also documents the schema_version separation in lib/gbrain-local-status.ts: the local CacheEntry stays at version 1, distinct from the doctor-output schema_version which we accept across versions in gstack-memory-helpers. Closes #1418 (credit @mvanhorn for surfacing the doctor + schema_v2 collapse). The fix landed pre-emptively in v1.29.x; this commit pins it with a stronger test. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/gbrain-local-status.ts | 6 +++++ test/gstack-memory-helpers.test.ts | 37 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/gbrain-local-status.ts b/lib/gbrain-local-status.ts index f546a93bc..540b3e5d6 100644 --- a/lib/gbrain-local-status.ts +++ b/lib/gbrain-local-status.ts @@ -51,6 +51,12 @@ export interface ClassifyOptions { } interface CacheEntry { + // Local-cache schema version, controlled by gstack. Not to be confused + // with `gbrain doctor --json` output schema_version (gbrain v0.25+ emits + // schema_version: 2). Doctor-output parsing lives in + // lib/gstack-memory-helpers.ts:freshDetectEngineTier and accepts both + // doctor-output versions. This cache stays strictly at version 1 — a + // future shape change here requires an explicit migration. schema_version: 1; status: LocalEngineStatus; cached_at: number; diff --git a/test/gstack-memory-helpers.test.ts b/test/gstack-memory-helpers.test.ts index f1d2bf379..a881c153b 100644 --- a/test/gstack-memory-helpers.test.ts +++ b/test/gstack-memory-helpers.test.ts @@ -341,4 +341,41 @@ describe("detectEngineTier", () => { const result = detectEngineTier(); expect(result.engine).toBe("supabase"); }); + + it("parses schema_version:2 doctor JSON via the exec path (regression for #1418)", () => { + // Stronger pin than the PATH-stripped fallback above: install a fake + // gbrain shim that successfully exits with status 1 (health_score < 100, + // mirroring real-world Supabase brains) and emits the v2 doctor JSON + // shape — schema_version: 2, status: "warnings", no top-level `engine`. + // The parser must still produce a usable EngineDetect by falling back + // to GBRAIN_HOME/config.json when `engine` is absent from doctor output. + const binDir = mkdtempSync(join(tmpdir(), "gstack-gbrain-shim-")); + const shim = join(binDir, "gbrain"); + writeFileSync( + shim, + `#!/bin/sh +if [ "$1" = "doctor" ]; then + cat <<'JSON' +{"schema_version":2,"status":"warnings","health_score":90,"checks":[{"name":"resolver_health","status":"ok","message":"42 skills"}]} +JSON + exit 1 +fi +if [ "$1" = "--version" ]; then + echo "gbrain 0.35.0.0" + exit 0 +fi +exit 0 +`, + { mode: 0o755 } + ); + process.env.PATH = `${binDir}:${process.env.PATH || ""}`; + writeFileSync( + join(testGbrainHome, "config.json"), + JSON.stringify({ engine: "pglite" }), + "utf-8" + ); + const result = detectEngineTier(); + expect(result.engine).toBe("pglite"); + rmSync(binDir, { recursive: true, force: true }); + }); });