Merge origin/main (v1.60.1.0) into garrytan/gstack-fix-wave

Semantic reconciliation with the parallel time-attack wave (#2264):
- careful: adopt main's anchored full-command whitelist (stricter — also
  catches comment-hiding), re-apply this wave's two hardenings on top
  (capital -[rR] in the flag cluster; exclude `(` and backtick from safe
  targets so $()/backtick substitution cannot ride the whitelist). Union
  of both waves' test batteries passes (main's test.each incl. comment
  case + this wave's substitution/capital-R/FP-pin cases).
- one-way-doors: main landed the singular noun unification (a2a447a1);
  keep this wave's superset (plural s? + --summary-stdin runtime wiring).
- gbrain-local-status: union of states — main's engine-locked (#2194,
  exit 124 PGLite lock) + this wave's thin-client (#2051). --is-ok keeps
  main's intent (engine-locked = STOP) and this wave's (thin-client =
  usable). Test harness unions both fake behaviors.
- sync-gbrain/setup-gbrain tmpls: both Step 1.5 branches kept; generated
  SKILL.md resolved via bun run gen:skill-docs (never hand-edited).
- VERSION/package.json -> 1.61.0.0 per bin/gstack-next-version (main took
  1.60.1.0; PR #2470 claims 1.60.2.0). CHANGELOG: wave entry renumbered
  1.61.0.0 on top of main's 1.60.1.0; careful/#2024 bullets updated to
  describe the delta vs current main. TODOS: union.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-07 14:36:05 -07:00
co-authored by Claude Fable 5
82 changed files with 7234 additions and 883 deletions
+21 -5
View File
@@ -6,13 +6,14 @@
* on PATH that emits canned exit codes + stderr matching the patterns the
* classifier looks for.
*
* Six status cases:
* Seven status cases:
* 1. no-cli — gbrain absent from PATH
* 2. missing-config — gbrain present, config.json absent (honors GBRAIN_HOME)
* 3. broken-config — gbrain present, config exists, stderr contains "config.json"
* 4. broken-db — gbrain present, config exists, stderr contains "Cannot connect to database"
* 5. timeout — probe exceeds GSTACK_GBRAIN_PROBE_TIMEOUT_MS with no recognized error (#1964)
* 6. ok — gbrain present, config exists, sources list returns valid JSON
* 6. engine-locked — PGLite CLI exits 124 because another process owns the DB (#2194)
* 7. ok — gbrain present, config exists, sources list returns valid JSON
*
* Plus cache behavior: hit, TTL expiry, invariant invalidation (HOME change,
* probe-timeout change), --no-cache bypass. Timeout tests keep runtime sane by
@@ -61,7 +62,7 @@ interface FakeEnv {
*/
function makeEnv(opts: {
withGbrain?: boolean;
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "throws" | "slow" | "thin-refusal";
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "engine-locked" | "throws" | "slow" | "thin-refusal";
withConfig?: boolean;
/** #2051: config carries gbrain's remote_mcp thin-client marker. */
thinClientConfig?: boolean;
@@ -109,7 +110,7 @@ function makeEnv(opts: {
}
function makeFakeGbrainScript(
behavior: "ok" | "broken-db" | "broken-config" | "throws" | "slow" | "thin-refusal",
behavior: "ok" | "broken-db" | "broken-config" | "engine-locked" | "throws" | "slow" | "thin-refusal",
): string {
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
// the (test-lowered) probe timeout, then would answer fine.
@@ -132,12 +133,14 @@ exit 0
? 'echo "Cannot connect to database: . Fix: Check your connection URL in ~/.gbrain/config.json" >&2'
: behavior === "broken-config"
? 'echo "Error: malformed config.json at ~/.gbrain/config.json" >&2'
: behavior === "engine-locked"
? 'echo "gbrain sources: connect timed out (default 10000ms; pass --timeout=Ns to override)." >&2'
: behavior === "throws"
? 'echo "unexpected gbrain failure" >&2'
: behavior === "thin-refusal"
? 'echo "Error: gbrain sources is not routable to the remote brain (thin-client of https://brain.example.com/mcp)" >&2'
: "";
const exitCode = behavior === "ok" ? 0 : 1;
const exitCode = behavior === "ok" ? 0 : behavior === "engine-locked" ? 124 : 1;
return `#!/bin/sh
if [ "$1" = "--version" ]; then
echo "gbrain 0.33.1.0"
@@ -234,6 +237,19 @@ describe("lib/gbrain-local-status — status classification", () => {
expect(localEngineStatus({ noCache: true })).toBe("broken-config");
});
it("returns 'engine-locked' when PGLite exits 124 with its own connect timeout (#2194)", () => {
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked", withConfig: true });
restoreEnv = applyEnv(env);
expect(localEngineStatus({ noCache: true })).toBe("engine-locked");
});
it("classifies a non-PGLite connect timeout as unreachable DB, not malformed config", () => {
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked", withConfig: true });
restoreEnv = applyEnv(env);
writeFileSync(env.configPath, JSON.stringify({ engine: "postgres", database_url: "postgres://fake" }));
expect(localEngineStatus({ noCache: true })).toBe("broken-db");
});
it("returns 'ok' when sources list succeeds", () => {
env = makeEnv({ withGbrain: true, gbrainBehavior: "ok", withConfig: true });
restoreEnv = applyEnv(env);