Commit Graph
577 Commits
Author SHA1 Message Date
SpinsirrandGitHub d574e843a8 fix(mcp): source-scope hardening for remote callers (#2881)
Three fixes in the same leak class (a remote caller reading or writing
outside its granted sources), for multi-source / multi-tenant brains:

1. dispatchToolCall now refuses remote calls that arrive without a
   resolved sourceId (missing_source_scope error envelope) instead of
   silently falling back to the shared 'default' source. Every shipped
   transport already passes sourceId explicitly (serve-http from the
   OAuth client row, http-transport from the legacy token grant, stdio
   from GBRAIN_SOURCE); reaching the fallback remotely always meant a
   programmatic caller skipped scope resolution — the bug class behind
   #1924 / #1371. Trusted local callers (remote === false) keep the
   historical fallback. Direct-dispatch tests updated to carry an
   explicit sourceId, matching the real transport contract.

2. log_ingest threads ctx.sourceId (same pattern as get_chunks /
   get_page), so ingest events are attributed to the caller's source
   instead of piling into 'default'. Engines already accept
   entry.source_id (v0.31.2).

3. get_ingest_log is source-scoped for remote callers via the
   linkReadScopeOpts collapse rule (scalar grant → [scalar]; federated
   grant → granted array); it previously returned the whole brain's
   ingest log to any read-scoped remote client, and ingest summaries can
   carry another source's private context. Trusted local callers keep
   the whole-brain view.

Tests: dispatch guard (refuse remote-without-source, keep local
fallback, guard ordering after op lookup) and end-to-end ingest-log
attribution + scoping over the real dispatch path, on PGLite.
2026-07-23 12:02:56 -07:00
22cb074943 fix(sync): honor the embedding_disabled sentinel as implicit --no-embed (#2879)
gbrain init --no-embedding writes embedding_disabled: true as a
deferred-setup sentinel, and init/import/embed honor it via
assertEmbeddingEnabled. sync's embed credential preflight (v0.41.6.0 D1)
only checked the --no-embed CLI flag, so every gbrain sync on a keyless
deferred-setup brain exited 1 demanding <PROVIDER>_API_KEY — including
orchestrated callers (gstack /sync-gbrain) that never pass --no-embed.

embed-preflight.ts's own skip protocol documents that the sentinel is
owned upstream of the credential check; this wires that contract into
sync by deriving noEmbed from CLI args + config in one exported pure
helper (resolveNoEmbed), covered by test/sync-no-embed-sentinel.test.ts.

Co-authored-by: Gawie van Blerk <gawie.vanblerk@emeraldlife.co.za>
2026-07-23 12:02:50 -07:00
d21f34e96d fix(search): project email citation metadata (#2873)
Co-authored-by: Amit Agarwal <5302320+amtagrwl@users.noreply.github.com>
2026-07-23 12:02:46 -07:00
AndreGitHubmerlin-drizzyenterprises[bot] <144527811+merlin-drizzyenterprises[bot]@users.noreply.github.com>
fa43907df4 fix(import): post-write read-back verification with durable ingest-log record (#2869)
A page write is not 'done' until it is readable back. After the import
transaction commits, verify the page resolves via getPage and its
content_hash matches what was just written. On mismatch or miss, fail
LOUDLY instead of reporting success, and record the failure in
ingest_log (best-effort) so it is durable and agent-inspectable rather
than a transient stderr message.

This catches the silent-desync class: the page file exists on disk (or
the git commit landed) but the DB index never picked the write up —
the operation previously reported success while the page stayed
invisible to every read path (get_page, search, query) until someone
noticed the gap manually.

Guard applies to both importFromContent (markdown) and importCodeFile.

Tests: new write-verify-guard suite (hermetic PGLite) covering the
happy path, index-miss, stale-hash, ingest_log record, and the put_page
operation surface; import-file.test.ts mock upgraded to simulate a
readable DB (writes are read-backable), matching the new guard.

PRJ-2026-032

Co-authored-by: merlin-drizzyenterprises[bot] <144527811+merlin-drizzyenterprises[bot]@users.noreply.github.com>
2026-07-23 12:02:41 -07:00
Garry Tan e0a208d7b7 Revert "fix(sources): stop source config re-wrapping into a growing JSON string scalar (#2829) (#2837)"
This reverts commit e36251c023.
2026-07-23 12:02:36 -07:00
Garry Tan 418357332f Revert "fix(embed): stamp gateway-resolved model in content_chunks.model, not compiled default (#2846)"
This reverts commit 5aa4795c04.
2026-07-23 12:02:36 -07:00
Garry Tan 45f85df8f4 Revert "fix(webhook): extract links for incremental push syncs (#2850)"
This reverts commit 11659743a2.
2026-07-23 12:02:36 -07:00
Garry Tan 9a70945152 Revert "fix(autopilot): give full-cycle dispatch a 30-minute timeout floor (#2852)"
This reverts commit b98fae9b61.
2026-07-23 12:02:36 -07:00
Garry Tan aea6df3da7 Revert "fix(onboard): stop repeating the same auto-remediation within a run (#2854)"
This reverts commit 054badbe60.
2026-07-23 12:02:36 -07:00
Garry Tan 35edd0e2d5 Revert "fix(scripts): capture check/shard rc before watchdog teardown in no-timeout fallback (#2864)"
This reverts commit e9a4fee97f.
2026-07-23 12:02:36 -07:00
Garry Tan 6388be2088 Revert "fix(list_pages): surface truncation instead of silently capping enumeration (#2865)"
This reverts commit 323610ecd7.
2026-07-23 12:02:36 -07:00
323610ecd7 fix(list_pages): surface truncation instead of silently capping enumeration (#2865)
list_pages clamps limit to max 100 (default 50) — deliberate server
protection, pinned in test/search-limit.test.ts. But the clamp was
SILENT: a caller whose limit was defaulted or clamped got a
full-looking array with no signal that rows were dropped, and with the
default updated_desc sort the dropped rows are always the OLDEST —
precisely what exhaustive consumers (audits, scans, backfills) exist
to find. Observed in the field: a source with 212 pages enumerated as
80 visible rows, hiding 26 pages from a compliance scan for days.

Fix, with no response-shape change (MCP consumers still get an array)
and no engine surface change (handler probes limit+1):

- handler probes one row past the effective limit; when the caller's
  limit was NOT honored (unset -> default, or clamped to cap) and rows
  were dropped, it warns on stderr for local (CLI) callers — same
  operator-facing channel as the put_page unknown-type hint, but
  without the isTTY gate: scripted callers are exactly the consumers
  that cannot detect truncation any other way, and stderr keeps stdout
  parseable. An explicit honored limit stays silent (ordinary
  pagination), as does a clamped-but-complete result. Remote (MCP)
  ctx never writes to stderr.
- LIST_PAGES_DESCRIPTION documents the cap and the exhaustive-listing
  recipe (sort=updated_asc + updated_after cursor) — the description
  is the signal channel MCP clients actually read.
- regression suite: default-limit truncation warns, honored limit
  silent, clamped-but-complete silent, remote silent, and the
  documented cursor recipe enumerates a corpus to completion.

Co-authored-by: YMYD <53603073+OJ-OnJourney@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:38:20 -07:00
e9a4fee97f fix(scripts): capture check/shard rc before watchdog teardown in no-timeout fallback (#2864)
On machines with neither gtimeout nor timeout on PATH, run-verify-parallel.sh
and run-unit-parallel.sh fall back to a bg-pid + sleep-watchdog cap. Both
read $? only after tearing the watchdog down (kill + wait on cap_pid), so the
sentinel .exit files recorded the killed watchdog's status — 143 — instead of
the check/shard's own exit code. Every run reported total failure (verify:
pass=0 fail=31; unit: rc=143 per shard) while every per-check/shard log
showed success.

Capture rc immediately after `wait $pid` in both scripts, and reap the
watchdog's sleep child (pkill -P, children-first — the same orphan quirk the
heartbeat cleanup documents) so the fallback stops leaking one sleep per
check/shard.

Regression tests force the fallback branch hermetically on any host via a
curated PATH with no timeout binaries: the verify dispatcher runs from a
tempdir copy with a stubbed `bun`, pinning exit 0 + all-zero sentinels when
checks pass and the check's own rc (not 143) when one fails; the unit wrapper
runs real two-shard fixture passes, pinning rc=0 sentinels and a real
failure's rc=1.

Co-authored-by: YMYD <53603073+OJ-OnJourney@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:38:15 -07:00
Sanchal RanjanandGitHub 054badbe60 fix(onboard): stop repeating the same auto-remediation within a run (#2854)
When the recommendation list is refreshed between remediation steps, a
remediation that doesn't clear its own health signal is reintroduced
under its stable id and attempted again, indefinitely on long runs.
Track attempted recommendation ids for the run and skip re-attempts.

Includes a behavioral regression test: a persistently-stuck signal is
attempted once, the loop terminates, and other remediations still run.
2026-07-23 11:38:10 -07:00
Sanchal RanjanandGitHub b98fae9b61 fix(autopilot): give full-cycle dispatch a 30-minute timeout floor (#2852)
Dispatch timeout was derived as interval*2 with a 5-minute floor, tuned
for light per-interval work. A full autopilot cycle routinely needs more
than 10 minutes at common intervals, so healthy full cycles were killed
mid-run. Full-cycle dispatch now gets a 30-minute floor; lighter
dispatches keep the interval-derived budget.

Adds a regression test for the full-cycle floor.
2026-07-23 11:38:05 -07:00
SongandGitHub 11659743a2 fix(webhook): extract links for incremental push syncs (#2850)
* test(webhook): pin sync extraction contract (#2849)

* test(webhook): target the submitted sync payload (#2849)

* fix(webhook): run extraction in sync job (#2849)

* fix(sync): align push trigger extraction (#2849)
2026-07-23 11:38:00 -07:00
5aa4795c04 fix(embed): stamp gateway-resolved model in content_chunks.model, not compiled default (#2846)
upsertChunks fell back to the compile-time DEFAULT_EMBEDDING_MODEL
('zeroentropyai:zembed-1') when a ChunkInput carried no explicit `model`.
The embed pipeline (src/commands/embed.ts) builds ChunkInputs without a
`model` field, so rows whose vectors were produced by the config-resolved
model (e.g. openai:text-embedding-3-large) were mislabeled with the
hardcoded default — corrupting the provenance that signature-drift
staleness and dimension-migration logic depend on.

Both engines now resolve the gateway's runtime embedding model once per
upsert and use it as the fallback, mirroring the existing resolve-then-
default pattern used for schema sizing. Regression test added (pglite);
verified via negative control that it fails against the old fallback.

This is a write-path change (upsertChunks), not a search-path change, so
retrieval eval replay is not applicable.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:37:06 -07:00
e36251c023 fix(sources): stop source config re-wrapping into a growing JSON string scalar (#2829) (#2837)
`sources.config` is a jsonb OBJECT column, but a read→write cycle that
JSON.stringify'd an already-stringified value re-wrapped it into a JSON string
scalar ("{}", "\"{}\"", ...) that grew one layer per write. parseSourceConfig
only unwrapped one layer, so the corruption never healed and federation/ACL
reads saw a string instead of the settings object.

- Add normalizeSourceConfig: a bounded (10-iteration) loop that JSON.parses
  while the value is a string and returns {} (with a console.warn) when the
  result is not a plain object. All six `UPDATE sources SET config` writers run
  their config through it before stringify, converging the stored value back to
  a jsonb object on the next write.
- parseSourceConfig now does the same bounded unwrap and warns once when more
  than one layer was found (one layer is the normal PGLite path).
- Add a `source_config_shape` doctor check that flags any sources row where
  jsonb_typeof(config) <> 'object', with the repair path.
- Unit-test the helper (object passthrough, 1-layer, 5-layer nested, garbage
  and over-bound inputs) and the doctor check (mock engine).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:37:02 -07:00
Ziyang GuoandGitHub 7bbd087cb7 fix(pages): restore soft-deleted rows on putPage (#2779) 2026-07-23 11:25:00 -07:00
TurgutKuralandGitHub e0d2cbf353 fix(doctor): distinguish entity timeline coverage from whole-brain density (#2761)
Issue #2298: 'gbrain doctor' surfaced two distinct timeline
metrics under the same user-facing 'timeline' label:

1. Entity timeline coverage (graph_coverage metric)
   - numerator: eligible entity pages WITH a timeline entry
   - denominator: eligible entity pages
   - 0-1 fraction, surfaced by graph_coverage check
2. Whole-brain timeline density (brain-score 0-15 component)
   - numerator: all pages WITH a timeline entry
   - denominator: all pages
   - 0-15 scale, surfaced by brain_score breakdown

These have DIFFERENT numerators/denominators. The old single
'timeline X%' label let a reader mistake the entity-scoped
percentage for whole-brain density.

Presentation/contract clarity only — scoring formula,
health weights, takes, source routing, extraction UNCHANGED.

- doctor.ts graph_coverage: 'timeline X%' -> 'entity timeline coverage X%'
- doctor.ts brain_score: 'timeline X/15' -> 'timeline density (all pages) X/15'
- cli.ts get_health: 'Timeline coverage (entity pages)' ->
  'Timeline density (all pages): X/15 (whole-brain brain-score component)'

Test: test/doctor-timeline-metric-labels-2298.test.ts uses a
synthetic in-memory PGLite fixture (NO private EriadorMu data):
4 total pages, 2 eligible entity pages, 1 entity page with a
timeline entry, 1 total page with a timeline entry.
Expected: entity coverage 1/2 = 50%; whole-brain density
1/4 -> round(25% * 15) = 4/15. Asserts the two metrics
render with distinct scoped labels and the brain-score component
is explicitly whole-brain (no 'entity' in its label). 5/5 pass.

Addresses #2298
2026-07-23 11:24:55 -07:00
7a1f61a31a fix: clear verified sync head sentinels (#2734)
Co-authored-by: Matthew Thompson <matthew@symmetric-consulting.com>
2026-07-23 11:24:50 -07:00
9b8b829ca5 fix(extract): --stale sweep runs the real resolver — basename resolution reaches stale pages (#2576) (#2717)
extractStaleFromDB still used the pre-#972 `includeFrontmatter ? resolver :
nullResolver` ternary. The synthetic resolver has no resolveBasenameMatches,
so the gate in extractPageLinks skipped the issue-#972 bare-wikilink pass
regardless of link_resolution.global_basename — the sweep stamped every page
as extracted while silently dropping its [[bare-name]] links. Same brain,
same pages: `extract --stale` created 0 links where `extract links --source
db` created 218.

- Always pass the real batch resolver; gate passes via extractPageLinks opts
  ({ skipFrontmatter: !includeFrontmatter, globalBasename }), mirroring
  extractLinksFromDB — including the codex-[P1] sourceId scoping.
- Bump LINK_EXTRACTOR_VERSION_TS (documented protocol) so pages stamped by
  the broken sweep re-flag stale and re-extract under the fixed logic.
- Regression tests: bare wikilink resolves on --stale with the flag ON;
  still drops with the flag OFF (back-compat). The #1768 fixture now derives
  its updated_at from LINK_EXTRACTOR_VERSION_TS instead of a hardcoded date,
  so future version bumps can't silently flip its version arm.

Fixes bug 1 + bug 3 of #2576. Bug 2 (DIR_PATTERN gaps) is a separate
whitelist design call, intentionally not addressed here.

Co-authored-by: YMYD <53603073+OJ-OnJourney@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:24:45 -07:00
c27b2e4b0f fix(put): refuse to overwrite a non-empty page with empty content (#2708)
An empty --content (most commonly a non-interactive caller that meant
file input — put has no --file flag — so the missing --content fell
back to reading empty stdin) silently blanked existing pages. put_page
now rejects an empty/whitespace-only body over an existing non-empty
page with invalid_params, pointing at `gbrain capture --file PATH
--slug SLUG` for file input; allow_empty: true (CLI: --allow-empty)
opts into an intentional blank. The guard read is scoped to the exact
(source_id, slug) row the write targets; new-slug creates and
soft-deleted-page overwrites stay allowed.

Co-authored-by: Matthew Thompson <matthew@symmetric-consulting.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:24:40 -07:00
Ziyang GuoandGitHub 9bcfa67748 fix(schema): count dead prefixes by slug (#2697) 2026-07-23 11:24:35 -07:00
16eb8cd06c fix(doctor): flag embed backfills without a worker (#2696)
Co-authored-by: gbrain-contrib <gbrain-contrib@example.com>
2026-07-23 11:24:30 -07:00
Garry Tan 92a3202198 Revert "fix(trajectory): stop negative metrics from inverting regression signals (#2621)"
This reverts commit 5dcf3e7b2f.
2026-07-23 11:24:25 -07:00
Garry Tan 8b7e30afcd Revert "perf(contextual-retrieval): bound per-chunk synopsis concurrency (#2628)"
This reverts commit 3454dca0b4.
2026-07-23 11:24:25 -07:00
Garry Tan 68e4cebd1a Revert "fix(health): count 'entity' pages in graph health metrics (#2639)"
This reverts commit 8fc93c8fac.
2026-07-23 11:24:25 -07:00
Garry Tan 8bbb19102c Revert "fix(test): isolate GBRAIN_HOME in hybrid-reranker integration test (#1527) (#2640)"
This reverts commit fe6850b067.
2026-07-23 11:24:25 -07:00
Garry Tan 9ae4e04d22 Revert "feat(ai): dashscope-rerank recipe (DashScope serves PLURAL /reranks under compatible-api) (#2644)"
This reverts commit 220af4b2d0.
2026-07-23 11:24:25 -07:00
Garry Tan fe2f2f6b2a Revert "fix: clarify PGLite data-dir lock contention (#2658)"
This reverts commit 0556dbdc2c.
2026-07-23 11:24:25 -07:00
Garry Tan fc169d9770 Revert "fix(import): normalize mixed-case slugs (#2695)"
This reverts commit 50406fc212.
2026-07-23 11:24:25 -07:00
Ziyang GuoandGitHub 50406fc212 fix(import): normalize mixed-case slugs (#2695) 2026-07-23 11:03:09 -07:00
zayandGitHub 0556dbdc2c fix: clarify PGLite data-dir lock contention (#2658) 2026-07-23 11:03:03 -07:00
220af4b2d0 feat(ai): dashscope-rerank recipe (DashScope serves PLURAL /reranks under compatible-api) (#2644)
DashScope's OpenAI-compatible rerank endpoint lives at
{base}/compatible-api/v1/reranks — PLURAL leaf, different base path from
the embedding surface (compatible-mode). Reusing llama-server-reranker
against DashScope forces users to hand-patch the recipe's '/rerank' leaf
in node_modules, which every upgrade silently reverts (and llama.cpp
genuinely serves singular /rerank, so changing that recipe would break
real llama.cpp users).

New dedicated recipe rides the v0.40.6.1 recipe-pluggable reranker path:
- id dashscope-rerank, base_url_default compatible-api/v1 (intl), ZE wire
- path '/reranks', default_timeout_ms 30s, 5MB payload ceiling
- models: only qwen3-rerank (live-verified 200; gte-rerank-v2 is rejected
  by the compat surface with 'Unsupported model for OpenAI compatibility
  mode', so it is deliberately not listed)
- separate recipe (not a reranker touchpoint on dashscope) because
  provider_base_urls is keyed by recipe id and the two capabilities need
  different prefixes — same topology as llama-server vs
  llama-server-reranker

Tests: recipe shape smoke mirroring recipe-llama-server-reranker.test.ts
(path/timeout/payload pins, /v1/v1 concat guard, auth resolve, sibling
recipe isolation). bun test test/ai/: 322 pass / 0 fail.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:02:53 -07:00
fe6850b067 fix(test): isolate GBRAIN_HOME in hybrid-reranker integration test (#1527) (#2640)
The four `hybridSearch — reranker enabled (reorder)` cases stub the gateway
at 1536 dims (DIMS). Since v0.36.3.0 hybridSearch resolves the embedding
column via loadConfig(), whose precedence is
cfg.embedding_dimensions > gateway dims > default. On any machine whose
~/.gbrain/config.json sets embedding_dimensions to something other than 1536
(e.g. text-embedding-3-small at 1280), the real config outranks the stub: the
1536-d stub vector fails the gateway dim check, the error is swallowed, search
falls back to keyword-only, and the reranker never runs (rerankerFn gets 0
docs, rerank_score undefined). Green in CI only because a fresh runner has no
config file — deterministic red on a contributor's machine.

Fix (test-only): isolate GBRAIN_HOME to an empty tmpdir in beforeAll so
loadConfig() returns null and the stub's dims win, then restore it and clean
up in afterAll. Same idiom as emptyHome() in
test/ai/gateway-probe-chat-model.test.ts.

Verified with a planted ~/.gbrain/config.json at 1280 dims: 2 pass / 4 fail
before, 6 pass / 0 fail after; still green with no config file. typecheck clean.

Fixes #1527

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:01:51 -07:00
Tyler RobinsonandGitHub 8fc93c8fac fix(health): count 'entity' pages in graph health metrics (#2639)
getHealth's entity_pages CTE and the top-linked-pages query only match the
legacy 'person' and 'company' types, so brains using the gbrain-base-v2
pack's 'entity' type report 0% entity link/timeline coverage in `gbrain
health` even when doctor's graph_coverage shows real coverage. Add 'entity'
to both queries in both engines (PGLite + Postgres, in lockstep per the
engine-parity rule) and extend the getHealth graph-metrics test with an
entity-typed page.

Validation: bun test test/pglite-engine.test.ts --test-name-pattern 'getHealth graph metrics' (5 pass).
2026-07-23 11:01:46 -07:00
spiky02plateauandGitHub 3454dca0b4 perf(contextual-retrieval): bound per-chunk synopsis concurrency (#2628)
Replace the strictly sequential per-chunk synopsis loop with a bounded
sliding worker pool (existing runSlidingPool helper). Results land in
chunk order via index-addressed writes; code chunks still bypass the
wrapper; embedding remains one page-level batch after all synopses.

New knob GBRAIN_CONTEXTUAL_CHUNK_CONCURRENCY, default 4, clamped to
[1,16]; 1 reproduces the prior sequential behavior exactly. Each chunk
task still acquires/releases the global synopsis rate-lease, which
remains the cross-worker governor; the lease id now travels from
acquire to release instead of shared mutable state, and lease waits
are abort-responsive.

At 20-45s per synopsis call, a 120-chunk transcript page previously
needed 60-90+ min wall time and routinely outlived job timeouts.
2026-07-23 11:01:39 -07:00
morlutoandGitHub 5dcf3e7b2f fix(trajectory): stop negative metrics from inverting regression signals (#2621) 2026-07-23 11:01:35 -07:00
Garry Tan dbca701008 Revert "fix(import): fall back to body H1 for title when frontmatter lacks title: instead of slug-derived junk (#2446) (#2495)"
This reverts commit 033fd24fe8.
2026-07-23 11:01:29 -07:00
Garry Tan 4b6cf32c9f Revert "fix(cycle): extract_facts guard requires live backing page, not just non-NULL entity_slug (#2497)"
This reverts commit 53c9086945.
2026-07-23 11:01:29 -07:00
Garry Tan 0c66715f90 Revert "fix(propose_takes): memoize empty extractions so zero-claim pages don't re-spend every cycle (#2514)"
This reverts commit 1233051a20.
2026-07-23 11:01:29 -07:00
Garry Tan 55af5fc091 Revert "fix: handle <think> reasoning tags in parseExtractorOutput (#2559)"
This reverts commit 2724c3b6c9.
2026-07-23 11:01:29 -07:00
Garry Tan 10b5746053 Revert "fix(storage): Supabase signed URLs — prepend /storage/v1 (#2565)"
This reverts commit 5a295bc293.
2026-07-23 11:01:29 -07:00
Garry Tan 5bee08c3c4 Revert "fix: honor explicit list_pages limit for local callers, warn on remote clamp, thread offset (#2591)"
This reverts commit 70ffe4a2a2.
2026-07-23 11:01:29 -07:00
Garry Tan f02919c041 Revert "fix(minions): default timeout for contextual reindex (#2611)"
This reverts commit fc1f88cdcb.
2026-07-23 11:01:29 -07:00
Garry Tan 66fa5fba22 Revert "fix(migrations): let force-retry escape completed ledger entries (#2616)"
This reverts commit e79b8d5780.
2026-07-23 11:01:29 -07:00
spiky02plateauandGitHub e79b8d5780 fix(migrations): let force-retry escape completed ledger entries (#2616)
statusForVersion short-circuited on any 'complete' entry before checking
the trailing 'retry' marker, so --force-retry appended an inert row and a
version marked complete with zero work done could never be re-run without
hand-editing completed.jsonl. Check retry-latest first: an explicit
--force-retry now yields 'pending' even past an earlier 'complete', while
a stray 'partial' after 'complete' still cannot regress the version.
2026-07-23 09:17:01 -07:00
spiky02plateauandGitHub fc1f88cdcb fix(minions): default timeout for contextual reindex (#2611) 2026-07-23 09:16:55 -07:00
70ffe4a2a2 fix: honor explicit list_pages limit for local callers, warn on remote clamp, thread offset (#2591)
gbrain list --limit 100000 silently returned 100 rows (default 50) with
no warning, and --offset was accepted but dropped at the op layer even
though PageFilters has supported it all along.

- Local CLI callers (ctx.remote === false, the same trust boundary that
  already bypasses scope enforcement) get an explicit limit above 100
  honored — full enumeration is a legitimate local operation.
- Remote MCP/OAuth callers keep the 100-row DoS cap, now loud: one
  logger.warn (stderr, stdout stays script-clean) with both numbers,
  parity with the three search-path clamp warnings.
- offset is declared as a param (so the CLI coerces it to number) and
  threaded to engine.listPages for real pagination.


Claude-Session: https://claude.ai/code/session_01Vswwe1y5fQbJWfbaSK3enT

Co-authored-by: deacon-botdoctor <291411030+deacon-botdoctor@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:16:50 -07:00