Skip to content

v0.41.4.0 wave: local providers + cross-platform stdin + gateway-routed dream judge (6 community PRs)#1377

Open
garrytan wants to merge 23 commits into
masterfrom
garrytan/community-pr-wave
Open

v0.41.4.0 wave: local providers + cross-platform stdin + gateway-routed dream judge (6 community PRs)#1377
garrytan wants to merge 23 commits into
masterfrom
garrytan/community-pr-wave

Conversation

@garrytan
Copy link
Copy Markdown
Owner

Summary

Community PR fix wave. Six contributor PRs land together (3 cherry-picked, 1 reworked, 2 closed-as-superseded/redundant with the value preserved). Builds on the v0.41 + v0.40.7.1 local-provider thread and finally closes the dream-cycle's hardcoded-Anthropic gap.

Local AI gets first-class treatment everywhere gbrain checks your setup:

Dream cycle stops being Anthropic-locked:

  • feat(dream): make significance judge provider-agnostic with OpenAI-compatible fallback #1349 (justemu) — REWORKED. Original PR took a parallel-fetch-adapter approach that bypassed gbrain's canonical gateway. Wave rewrites the same goal via gateway.chat() adapter so any registered provider (DeepSeek, OpenRouter, Voyage, Ollama, llama-server, ...) reaches the dream judge via one config line: gbrain config set models.dream.synthesize_verdict deepseek:deepseek-chat.

Cross-platform ergonomics:

Closed without merging (value preserved):

  • feat(put_page): add --file param to bypass Windows pipe buffer limitation #1365 (ecat2010)put_page --file was redundant with the already-shipped gbrain capture --file PATH --slug SLUG (v0.39.3.0; reads files as a Buffer with binary-NUL guard and provenance write-through). PR also had a ship-blocking bug (handler block landed in get_page instead of put_page). Closed with a doc improvement in this wave that points users at the canonical command from gbrain put --help.

Lock the bug class shut:

  • New CI guard scripts/check-gateway-routed-no-direct-anthropic.sh prevents synthesize.ts and think/index.ts from regressing back to new Anthropic(). Handles every TypeScript import shape (default, named, namespace, mixed-type-value, dynamic). 7-shape regression matrix verifies every bypass attempt is caught.

Test Coverage

Coverage audit (subagent): 85% (gate PASS, above 80% target). 0 gaps below threshold. 263/263 wave-affected tests pass across 15 files.

R1+R2+R3+R4 IRON-RULE regressions all pinned:

  • R1 — get_page accepts calls without content (closes the PR feat(put_page): add --file param to bypass Windows pipe buffer limitation #1365 bug class)
  • R2 — put_page schema content stays required: true
  • R3 — Gateway-adapter parsed-verdict semantic parity (NOT byte-identical; codex correctly flagged byte-identity as meaningless)
  • R4 — Cross-platform stdin behavior + source-grep belt-and-suspenders

New test files:

  • test/cycle/synthesize-gateway-adapter.test.ts (11 cases: A1-A9 unit + R3 parity + R3 corollary)
  • test/cycle/regression-pr-wave-r1-r2-r4.test.ts (R1+R2+R4 pins)

Extended test files:

  • test/e2e/dream-synthesize-pglite.test.ts — new "mid-run AIConfigError catch" E2E case + hermeticity hardening (withoutAnthropicKey now overrides GBRAIN_HOME)

Pre-Landing Review

9 informational findings, 0 critical. PR Quality Score: 7.0.

3 absorbed in-wave:

  • Stale gbrain models doctor tip text ("spends ~1 token" → "spends a minimal request per configured chat/embed/rerank surface").
  • CI guard regex tightened — adversarial review found bypasses via named imports ({ Anthropic }), namespace imports (* as A), mixed-type-value ({ type Msg, Anthropic }), and dynamic imports. All 7 import shapes now have hermetic regression coverage.
  • Reranker probe timeout now matches live search precedence chain (per-call > config > recipe > bundle) via new resolveLiveRerankerTimeoutMs(engine) helper. Probe could lie either direction before — false-ok when production would have timed out at config-set 1s, false-fail when production had a higher configured timeout.

Filed as TODO (deferred):

  • probeEmbeddingReachability should honor embedding recipe default_timeout_ms (sibling to the reranker probe fix; requires widening EmbeddingTouchpoint to carry the field).
  • FREE_LOCAL_*_PROVIDERS zero-pricing bypassable via redirected LLAMA_SERVER_*_BASE_URL env vars. Pre-existing posture; couples with the unification TODO.

Documentation honesty fix:

  • synthesize.ts:makeJudgeClient JSDoc narrowed — claimed "tolerates the array-of-blocks shape for future flexibility" but the mapping silently drops non-text blocks. Now explicitly states "TEXT-flattens only" so future callers wiring tool-use through this client extend the mapping instead of relying on silent drops.

Adversarial Review Synthesis

  • Claude subagent (always-on): 8 findings. 2 wave-scope fixed (CI guard tightening + JSDoc narrowing). 1 already-pre-existing posture (SSRF perimeter — same shape as existing LLAMA_SERVER_BASE_URL). 5 informational / out-of-scope.
  • Codex adversarial (always-on): 3 findings. 1 P2 fixed (reranker probe timeout precedence). 2 P1 filed as TODO (budget bypass = known unification follow-up; synthesize AIConfigError-as-worth-false = wave-INTENDED design per the eng-review plan's T5 implementation notes).
  • Codex structured review (large-diff path): 1 P3 finding caught a real bypass in the tightened CI guard regex (import { type Message, Anthropic } slipped through [^t][^y]*). Fixed in-wave with clause-level parsing + macOS BSD sed POSIX class fix.

Codex gate: PASS (no P1 findings).

Plan Completion

13/13 plan items: 10 DONE, 0 CHANGED, 2 DEFERRED by design (T11 close-PRs + T13 ship — both happen post-PR-creation), 1 UNVERIFIABLE-but-being-executed (T12 verification chain = this /ship).

Full plan + decision audit: ~/.claude/plans/system-instruction-you-are-working-cozy-pancake.md.

Verification Results

gbrain is a CLI tool, not a web app. /qa-only URL-probing doesn't apply. Verification handled via bun run verify (typecheck + 14 prechecks) + wave-affected test suites (263/263 pass).

E2E note: 6 E2E failures observed (cycle.test.ts, dream.test.ts) are PRE-EXISTING on master — verified by checking out origin/master and reproducing identically. PR #1367 (v0.41.0.0 minions cathedral, just landed) introduced them. Not in-branch failures. Mechanical.test.ts (78/78), wave-affected E2E (12/12 dream-synthesize-pglite), and the rest of the E2E suite (840 cases) pass.

TODOS

  • Added: probeEmbeddingReachability recipe-timeout follow-up, FREE_LOCAL_*_PROVIDERS bypass concern (couples with unification TODO).
  • Marked complete: none (this wave is implementation-only — no prior TODOs targeted by the diff).

Documentation

CLAUDE.md, README, docs/ai-providers/llama-server-reranker.md, docs/integrations/embedding-providers.md, and llms.txt / llms-full.txt all updated in-wave. /document-release ran clean — no follow-up commit needed.

Test plan

  • bun run verify — typecheck + 14 prechecks clean
  • Wave-affected tests: 263/263 pass across 15 files
  • E2E dream-synthesize-pglite.test.ts — 12/12 pass including new AIConfigError catch case
  • E2E mechanical.test.ts — 78/78 pass
  • Pre-existing E2E failures verified pre-existing on master (PR v0.41.0.0 feat(minions): fleet you supervise (4 field bugs + cathedral) #1367); not gating
  • CI guard 7-shape bypass regression matrix verified
  • Reviewer manual test: try gbrain config set models.dream.synthesize_verdict deepseek:deepseek-chat + DEEPSEEK_API_KEY=... gbrain dream --phase synthesize --dry-run
  • Reviewer manual test: try echo "content" | gbrain put windows-test-slug on Windows (CI matrix doesn't cover; manual confirmation appreciated)
  • Full CI suite

Co-Authored-By: tobbecokta 34135750+tobbecokta@users.noreply.github.com
Co-Authored-By: kohai-ut chris@tincreek.com
Co-Authored-By: justemu 206393437+justemu@users.noreply.github.com
Co-Authored-By: ecat2010 90021101+ecat2010@users.noreply.github.com

🤖 Generated with Claude Code

tobbecokta and others added 23 commits May 24, 2026 11:56
…eads

`readFileSync('/dev/stdin', 'utf-8')` works on Unix but fails on Windows
(Git Bash, PowerShell, cmd) with `ENOENT: no such file or directory,
open '/dev/stdin'`. Windows doesn't expose `/dev/stdin` as a filesystem
path.

Reading file descriptor 0 directly (`readFileSync(0, 'utf-8')`) is the
documented Node.js idiom and works on every platform. No behavior change
on Unix — same syscall path, same semantics.

Repro on Windows before the fix:
  echo "test" | gbrain put my-page
  ENOENT: no such file or directory, open '/dev/stdin'

After: round-trip put/search/delete works on Windows Git Bash.
…via llama.cpp

Adds local reranker support so users can point gbrain's reranker call at their
own llama.cpp server instead of ZeroEntropy's hosted API. One new recipe
(`llama-server-reranker`), a `path?: string` + `default_timeout_ms?: number`
extension on `RerankerTouchpoint`, env passthrough wiring, budget-tracker
`FREE_LOCAL_RERANK_PROVIDERS` set so `--max-cost` callers don't TX2 hard-fail on
local rerank, and a doctor-probe divergence fix (probe and live search now read
the same `search.reranker.model` path via `loadSearchModeConfig` + `resolveSearchMode`).

ZE-hosted users are unchanged. Voyage / Cohere / vLLM rerankers stay out of
scope — different wire shapes need adapter hooks designed against their actual
shapes in a follow-up plan.

Verification:
- `bun run verify` (typecheck + 13 pre-checks): clean
- `bun run check:all` (15 historical checks): clean
- 107/107 expect() calls pass across 5 affected test files
- /codex review against the full diff: GATE PASS (caught one [P2] /v1 path
  doubling bug pre-merge; fixed by changing recipe path to leaf `/rerank`)
- Claude adversarial subagent: 7 net-new findings filed as v0.40.7+ TODOs
  (none currently exploitable; hardening for future contributor traps)

Test surface (107 cases, 5 files):
- test/ai/rerank.test.ts: path override (exact URL match), default_timeout_ms
  honored, empty models[] accepts any id, ZE regression
- test/ai/recipe-llama-server-reranker.test.ts: recipe shape regression guard
  + base_url + path concat assertion (codex-caught /v1/v1/ regression)
- test/search-mode.test.ts: timeout precedence chain (per-call > config >
  recipe > bundle), ZE no-recipe-default regression, unknown provider fallthrough
- test/models-doctor-reranker.test.ts: divergence-fix helper across DB-plane
  read, mode default, disabled, override, DB-error graceful fallback
- test/core/budget/budget-tracker.test.ts: free-local rerank pricing + arbitrary
  model id + chat-kind TX2 hard-fail preserved

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…r-reranker)

The hand-curated llms-config.ts doc map never included docs/ai-providers/, so
both zeroentropy.md (since v0.35.0.0) and the new llama-server-reranker.md were
invisible to the AI-facing llms.txt / llms-full.txt index. Adds an "AI providers"
section with both. Marked includeInFull: false (setup walkthroughs belong in the
index but would push the single-fetch bundle past FULL_SIZE_BUDGET) — same
treatment CHANGELOG.md gets.

Caught by the /ship document-release subagent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
doctor --remediation-plan and autopilot both judged the embedding
provider with a hosted-only key check, so a brain on ollama: or
llama-server: was reported "blocked" on a missing API key it never
needed, contradicting doctor --json's 100%-coverage health.

Extract a shared embeddingProviderConfigured() helper into
brain-score-recommendations.ts: empty auth_env.required (local
providers) is configured with no key; hosted providers check their
OWN required key. Both producers (doctor, autopilot) call it,
killing the DRY violation that caused the bug. Hosted brains with a
missing key still block.
A --max-cost-bounded embed/reindex job configured for ollama: or
llama-server: TX2 hard-failed with no_pricing because
lookupEmbeddingPrice has no entry for local models. Add
FREE_LOCAL_EMBED_PROVIDERS (sibling to FREE_LOCAL_RERANK_PROVIDERS)
so a pricing miss on a local-inference provider returns $0 instead
of null. lmstudio/litellm intentionally excluded.
A down/misconfigured local embed server was invisible until first
embed. Add probeEmbeddingReachability() (mirrors the reranker probe):
a 1-input embed with a 5s abort timeout, classified via classifyError,
under a new 'embedding_reachability' touchpoint, gated on the
zero-network config probe returning ok first.
codex review caught a false positive: HOSTED_EMBED_KEY_CONFIG mapped
VOYAGE_API_KEY/GOOGLE_GENERATIVE_AI_API_KEY to config fields, but
buildGatewayConfig only threads openai/anthropic/zeroentropy config
keys into the gateway env. A Voyage/Google brain with the key only in
config.json would be judged "configured" and dispatch an embed.stale
job that then fails auth at the gateway. Drop those two from the map so
the producer closures resolve them by env var only, matching what the
gateway can actually use. Pinned by a regression test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…provider support

Replaces the hardcoded `new Anthropic()` client in the dream-cycle synthesize
phase with a gateway-routed JudgeClient adapter. Mirrors the v0.35.5.0 pattern
that closed #952 for runThink: construction-time provider/key probe returns null
on a clear miss (cheap pre-flight); the verdict loop wraps the chat call in
try/catch for AIConfigError mid-run.

Any provider with a registered gateway recipe (Anthropic, DeepSeek, OpenRouter,
Voyage, Ollama, llama-server, etc.) is now reachable via:

    gbrain config set models.dream.synthesize_verdict <provider>:<model>

The canonical config key `models.dream.synthesize_verdict` (per PER_TASK_KEYS
in src/core/model-config.ts) is used unchanged. The exported JudgeClient
interface signature is preserved for test-seam stability.

The original community PR (#1349) shipped a custom fetch adapter that
bypassed the gateway entirely. This reworked landing routes through the
canonical seam so future provider additions automatically benefit, and a
CI guard (T7) will land in this wave to prevent the bug class from
re-opening (the same one that bit src/core/think/index.ts before v0.35.5.0).

Co-Authored-By: justemu <206393437+justemu@users.noreply.github.com>
…t parity

11 cases pin the gateway-routed JudgeClient adapter from T5:

- A1: makeJudgeClient returns null on missing Anthropic key (legacy short-circuit preserved)
- A2: returns a JudgeClient when chat provider is reachable
- A3: JudgeClient.create routes through gateway.chat (via __setChatTransportForTests)
- A4: ChatResult.text → Anthropic.Message.content[0].text mapping
- A5: empty text from gateway → graceful empty-text Anthropic.Message
- A6: non-AIConfigError from gateway propagates to caller (no swallow)
- A7: AIConfigError from gateway propagates as AIConfigError (caught per-transcript in production loop)
- A8: makeJudgeClient returns null on unknown provider prefix
- A9: returns a JudgeClient for non-anthropic providers without env-probing (delegates to gateway at call time)
- R3: parsed-verdict SEMANTIC parity — gateway-routed and legacy SDK-shape JudgeClients produce same {worth_processing, reasons} given identical canned LLM text
- R3 corollary: unparseable LLM output → both paths fall through to cheap-fallback verdict

Codex flagged byte-identical-Anthropic.Message as a meaningless gate; R3 is
parsed-verdict semantic parity instead. Mirror pattern of
test/think-gateway-adapter.test.ts for cross-site consistency with the
v0.35.5.0 runThink migration.
… files

New scripts/check-gateway-routed-no-direct-anthropic.sh greps two guarded
files (src/core/cycle/synthesize.ts and src/core/think/index.ts) for
`new Anthropic()` constructor calls and runtime imports of @anthropic-ai/sdk.
Type-only imports (`import type Anthropic from '@anthropic-ai/sdk'`) stay
allowed because both files use Anthropic.Message / .MessageCreateParamsNonStreaming
as adapter types.

Comment lines (starting with `//` or ` *`) are excluded so historical
references in JSDoc don't false-fire. Negative test in this commit's
verification confirms: injecting `new Anthropic()` into synthesize.ts
makes the guard exit 1 with a clear error pointing at the gateway adapter
pattern; reverting restores the OK state.

Wired into both `bun run verify` and `bun run check:all`. Closes the bug
class that bit synthesize.ts in PR #1349 (which would have shipped a
parallel fetch stack instead of routing through the canonical gateway).
The same class previously bit think/index.ts and was fixed structurally
in v0.35.5.0; this guard prevents either file from regressing.

Extend GUARDED_FILES in the script when migrating another file off
direct SDK construction.
…-file

Extends the put_page op description (surfaced by `gbrain put --help`) with a
one-line pointer to `gbrain capture --file PATH --slug SLUG` for the file-
as-input use case. Capture (v0.39.3.0) is the canonical Windows-pipe-buffer
escape route: reads files as a Buffer first, scans the first 8KB for NUL bytes
to refuse binary content, decodes to UTF-8 only after the safety check, and
adds provenance write-through.

Lands the user-facing value the closed PR #1365 was reaching for, without
duplicating the CLI surface. Credits the original contributor.

Co-Authored-By: ecat2010 <90021101+ecat2010@users.noreply.github.com>
…ding

Per the wave's eng-review plan (IRON RULE — mandatory):

  R1 — get_page handler accepts calls without `content` param. Pre-wave
       PR #1365 landed its `!p.content → throw` check in the WRONG handler
       (get_page instead of put_page), which would have broken every read
       in the system. Pin: get_page MUST NOT require content + the schema
       carries no `content` or `file` param.

  R2 — put_page schema content stays `required: true`. PR #1365 also
       flipped `content` from required→optional in the schema. Pin: the
       contract stays at `required: true` + the closed PR's `file` param
       is NOT in the schema.

  R4 — Cross-platform stdin via fd 0 (PR #1325 regression pin). Source-grep
       asserts src/cli.ts uses `readFileSync(0, ...)` and NOT the legacy
       `readFileSync('/dev/stdin', ...)`. Belt-and-suspenders pattern
       assertions confirm the parseOpArgs branch shape (cliHints.stdin
       check, 5MB cap, isTTY gate) hasn't drifted.

R3 (gateway-adapter parsed-verdict parity) lives in the sibling file
test/cycle/synthesize-gateway-adapter.test.ts.
…icity

After T5's gateway-adapter rework, the "no API key" verdict text changed from
'no ANTHROPIC_API_KEY for significance judge' to
'no configured provider for verdict model: <model>' (broader + names the
actual model so the user sees WHICH provider failed). Update both assertions
that check the old text.

Hermeticity bug fix in the same commit: `withoutAnthropicKey` previously only
cleared the env var. After the rework, `makeJudgeClient` ALSO checks
`loadConfig().anthropic_api_key` (same hasAnthropicKey() pattern think/index.ts
uses since v0.35.5.0). If the developer running the test has the key set in
~/.gbrain/config.json, the test would behave non-deterministically. Fix:
override GBRAIN_HOME to a fresh tmpdir for the duration of the body, restore
on return (even on throw).
…-end

Drives runPhaseSynthesize against a real PGLite engine with the gateway
chat transport stubbed to throw AIConfigError on every call (simulates a
revoked/misconfigured provider surfacing mid-run). Asserts:

  - Phase does NOT crash; converts the throw to a per-transcript verdict
    with worth=false and reasons[0] matching "gateway error: ...".
  - status='ok' so subsequent transcripts in the loop would continue
    being judged (not visible in 1-transcript test, but the loop shape is
    proven not to abort).

Pre-rework (T5), this code path didn't exist — judgeSignificance threw
directly to runPhaseSynthesize and crashed the whole phase. Pin so a
future regression that removes the try/catch fires loudly.
Two additions to the Key files section:

- src/core/cycle/synthesize.ts — appends a v0.41+ paragraph documenting
  the gateway-adapter rework (makeJudgeClient + AIConfigError catch loop +
  canonical config key + JudgeClient interface preserved + CI guard
  reference + test file references).

- scripts/check-gateway-routed-no-direct-anthropic.sh — new entry
  documenting the CI guard's contract, scope, and how to extend
  GUARDED_FILES when migrating another file off direct SDK construction.

CLAUDE.md drives /sync-gbrain and llms.txt generation; both need the
wave's annotations to land BEFORE the llms regeneration step (T10).
Refreshes the auto-generated llms.txt bundles to pick up the CLAUDE.md
annotations landed earlier in this wave (gateway-adapter synthesize.ts
+ check-gateway-routed-no-direct-anthropic.sh + the cherry-picked
llama-server-reranker recipe). Pinned by test/build-llms.test.ts.
…anker

v0.40.6.1 introduced `llama-server-reranker` (21 chars), which overflowed
formatRecipeTable's static 14-char PROVIDER column. When the id is longer
than the column, padEnd is a no-op — the row starts with the tier name
directly, no space delimiter. test/providers.test.ts 'each recipe appears
at most once' iterates every recipe and asserts at least one row starts
with `${id} ` or `${id}  `; with no space after `llama-server-reranker`,
the assertion fails and the recipe appears effectively missing from the
human-readable list.

Fix: compute column width dynamically as `max(14, max(id.length) + 1)` so
every id is followed by at least one space, regardless of length. Also
widens the separator rule to match. 14 stays as the floor so the existing
short-id rows (openai 6, ollama 6, anthropic 9, ...) keep their familiar
layout when llama-server-reranker isn't in the active recipe set.

10/10 cases in test/providers.test.ts pass after the fix.
…mbed timeout TODO

Two pre-landing review absorptions:

- `src/commands/models.ts:154` — the help-text tip said `gbrain models doctor`
  "spends ~1 token per model" but the wave added an `embed(['probe'])` call
  AND a reranker probe. Generalize to "spends a minimal request per configured
  chat/embed/rerank surface" so the cost expectation matches reality.

- `TODOS.md` — file a follow-up to widen `default_timeout_ms` from
  RerankerTouchpoint to EmbeddingTouchpoint so `probeEmbeddingReachability`
  doesn't hardcode 5000ms while the sibling reranker probe reads the
  recipe's configured timeout. Local CPU embedding endpoints (llama-server)
  hit the same cold-start curve as Qwen3-Reranker-4B; workaround today is
  "re-run the probe" per the existing JSDoc.

Other informational findings from pre-landing review either match
established patterns (no behavioral test for `probeEmbeddingReachability`,
matching `probeRerankerReachability`), are intentional choices documented
in JSDoc (the `as unknown as Anthropic.Message` cast), or are micro-perf
in non-hot paths (autopilot's 4 sequential `getConfig` awaits per
5-minute tick). All non-blocking.
…t JSDoc

Adversarial review caught two soft spots in the wave's new contracts:

1. `scripts/check-gateway-routed-no-direct-anthropic.sh` only matched the
   default-import shape `import Anthropic from '@anthropic-ai/sdk'`. A future
   contributor (or, more realistically, a future refactor) could bypass with:
     - `import { Anthropic } from '@anthropic-ai/sdk'`
     - `import { Anthropic as A } from '@anthropic-ai/sdk'`
     - `import * as Anthropic from '@anthropic-ai/sdk'`
     - `const x = await import('@anthropic-ai/sdk')`
   Tightened the regex to match ANY value-shaped import from the SDK module
   (excluding only the explicit `import type ... from '@anthropic-ai/sdk'`
   form which the adapter's Anthropic.Message return type needs). Added a
   second grep for dynamic imports. Verified all four bypass shapes now
   trigger the guard against synthesize.ts; type-only import still passes.

2. `synthesize.ts:makeJudgeClient` JSDoc claimed the adapter "tolerates the
   array-of-blocks shape for future flexibility" — but the mapping flattens
   ONLY text blocks; `tool_use`, `tool_result`, image blocks silently
   become empty strings. Today only `judgeSignificance` calls this and it
   only sends string content, so no behavior bug. But the comment was
   marketing future flexibility the code doesn't deliver. Narrowed to call
   out the silent-drop and say to extend the mapping if a future caller
   wires non-text content through.

Both wave-scope: the CI guard was added by the wave, the JSDoc was added
by the wave's T5 rework. Adversarial review caught them before merge.
…ence chain

Codex Pass-9 adversarial review caught a probe-vs-production divergence:
production `hybridSearch` resolves reranker timeout via the full chain
(per-call > config > recipe > bundle) by going through
`loadSearchModeConfig + resolveSearchMode`, but `probeRerankerReachability`
was reading ONLY the recipe's `default_timeout_ms` — so an operator who
set `search.reranker.timeout_ms=1000` would see doctor wait 30s and report
"reachable" while production search timed out at 1s and fail-opened.
A higher configured timeout produces the opposite false failure (probe
gives up at 5s when production would have waited longer).

Fix: extract `resolveLiveRerankerTimeoutMs(engine)` parallel to the
existing `resolveLiveRerankerModel(engine)` — same precedence chain,
same DB-plane consistency posture. The probe now reads the SAME timeout
live search reads, on the same lookup path.

The codex P1 finding about `FREE_LOCAL_*_PROVIDERS` zero-pricing being
bypassable via redirected `LLAMA_SERVER_BASE_URL` is filed as a TODO under
community-pr-wave follow-ups — couples with the existing
FREE_LOCAL_PROVIDERS unification TODO so both close in one v0.41+ PR.
Codex structured review [P3] caught a bypass in the freshly-tightened
gateway-routed guard:

  import { type Message, Anthropic } from '@anthropic-ai/sdk';
  new Anthropic();

The previous regex `^\s*import\s+[^t][^y]*from ...` was meant to exclude
`import type ...` but stops at the `y` in `type` inside the brace list,
silently allowing the value-import `Anthropic` through. Two fixes:

1. Replace the brittle regex-based type-exclusion with a clause-level
   parse: extract the brace-list specifiers, allow the import iff EVERY
   non-empty specifier is `type`-prefixed. Catches mixed-import bypasses
   (`{ type Foo, Bar }`) while keeping all-type braces (`{ type Foo, type Bar }`)
   passing. Default + namespace imports remain always-value-shaped.

2. Replace `\s` with POSIX `[[:space:]]` in the sed extract — macOS BSD sed
   doesn't honor `\s` in extended-regex mode (it silently no-ops the pattern
   so `specifiers` comes back empty and the script falls through to the
   default/namespace branch's wrong error message).

Hermetic 7-shape regression matrix now verifies every TypeScript import
shape against the expected ALLOW/BLOCK verdict; all 7 pass:
- ALLOW: `import type Anthropic from '...'`
- ALLOW: `import type { Foo } from '...'`
- ALLOW: `import { type Message, type Foo } from '...'`
- BLOCK: `import { type Message, Anthropic } from '...'`
- BLOCK: `import { Anthropic } from '...'`
- BLOCK: `import Anthropic from '...'`
- BLOCK: `import * as A from '...'`

Subshell-trap fix in the same commit: the previous "exit 1 inside while-pipe"
pattern doesn't propagate to the outer `$?` because the pipe spawns a
subshell. Switched to a tmpfile-flagged sentinel so the verdict survives
the subshell boundary cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants