* fix(pglite): in-place WAL auto-repair for the Aborted() startup crash (#223, #1670, #2575)
The 'macOS 26.x WASM bug' was a misdiagnosis: an unclean shutdown (typically
the OS-upgrade reboot) tears the data dir's WAL, and every subsequent open
fails WAL replay inside WASM with an opaque RuntimeError: Aborted(). This
ports the pg_resetwal recovery upstream rejected (electric-sql/pglite#994,
by @yestheboxer) and wires it into connect() as bounded auto-repair:
- src/core/pglite-resetwal.ts: pg_resetwal for PG17 NodeFS dirs, fail-closed
layout validation, atomic+durable writes (tmp+fsync+rename), idempotent.
- src/core/pglite-repair.ts: whole-pg_wal-dir rename backup (zero transient
disk), overwrite-order restore with mtime guard, cooldown sidecar +
episode-scoped backup retention (newest 3 episodes), and a never-throws
engine seam. Kill-switch: GBRAIN_PGLITE_WAL_REPAIR=off.
- pglite-engine.ts: verdict rename macos-26-3 -> wasm-abort, classifier now
matches the real production message (it previously fell to 'unknown'),
corrupt-beats-wasm precedence preserved, honest per-outcome error copy
incl. the failed-not-restored arm, and repair only under a cleanly-acquired
lock (new LockHandle.reaped provenance; never after reaping a holder).
- gbrain pglite-repair: manual dry-run/repair command (validate-before-lock,
serve/reaped refusals, no --force by design).
- doctor: pglite_data_dir fs-check with recurrence escalation and backup
inventory when a PGLite brain fails to connect.
- reinit-pglite: embedding flags default from file-only config so the
recovery ladder's rebuild rung works bare mid-outage.
- stringifyPgliteInitError: message-less Emscripten ErrnoError objects no
longer surface as [object Object].
Regression-tested against real brains: corrupt every WAL segment (truncate
and garbage variants), reopen, auto-repair fires, original rows readable,
process.exitCode stays contained (#2084).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(pglite): replace the macOS-26.x misdiagnosis with the corrupt-WAL recovery ladder
README + INSTALL.md shipped (via #1671) the claim that PGLite is incompatible
with macOS 26.x and that a Bun/WASM fix would restore it. The real cause is
torn WAL state from the upgrade reboot, now auto-repaired in place. Rewrites
those sections around the recovery ladder (auto-repair -> gbrain pglite-repair
-> reinit-pglite -> engine switch; native-Postgres recipe kept, credit
@roysaurav), adds the ENGINES.md troubleshooting section, updates the
KEY_FILES.md entries to current truth, files the two follow-up TODOs
(SIGTERM engine-close extension; pglite upgrade blocker), and regenerates
the llms bundles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pglite): harden WAL auto-repair (pre-landing + adversarial review)
Review-army (security/testing/maintainability/perf) + Claude & Codex
adversarial passes on the WAL-repair wave. Correctness + safety hardening,
no behavior change to the happy path:
- Live-writer safety: repair refuses any reaped lock acquisition, a corrupt
(unknowable-liveness) reap writes a cross-process quarantine marker that
gates auto-repair AND the manual command for 10 min, isProcessAlive treats
only ESRCH as dead (EPERM/malformed-pid read as alive), and a live
postmaster.pid (native Postgres) is refused. Lock heartbeat + initial write
are atomic (tmp+rename) so a torn read can't misclassify a healthy holder;
an in-flight acquisition is no longer mistaken for corrupt.
- resetWal verifies the stored pg_control CRC before trusting/re-signing it —
a damaged control file routes to rebuild instead of laundering corrupt
checkpoint counters under a fresh CRC. Atomic 'wx' writes (no symlink
follow), whole-pg_wal-dir rename backup, 64MB seg-size cap.
- Honest failure reporting: repairPgliteWal threads the real restore result
out via WalRepairError so the 'failed-restored' vs 'failed-not-restored'
message never lies; the not-restored copy names the correct restore paths.
- Episode lifecycle: episodes close on the next healthy connect (not just on
a verified repair), a gutted (restored) backup loses its pin, stale (>24h)
episode backups aren't reused, and the cooldown also caps repaired-only
crash loops. Empty backup dirs are pruned on refusal.
- Command: rejects unknown flags and valueless --path (a destructive command
must not silently mis-parse), confirm prompt goes to stderr (stdout stays
clean for --json), embedding-flag defaults come from the config file only.
- Symlink confinement extended to global/; sidecar reuse path validated
(prefix + no '..' + must still hold pg_wal); sidecar writes atomic.
- doctor recurrence escalation counts all attempts; data dir absolutized.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(pglite): current-state KEY_FILES + WAL-repair follow-up TODOs
KEY_FILES.md pglite entries updated to the hardened truth (reap marker +
quarantine, atomic writes, CRC gate, global-symlink refusal, WalRepairError,
episode lifecycle). TODOS.md files the deferred judgment-call follow-ups
(unclean-shutdown gate on auto-repair; non-gbrain pglite consumer boundary;
mixed-version torn-lock double-read).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: bump version and changelog (v0.42.75.0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes#3835
Use a canonical positive pull flag for sync and autopilot-cycle jobs while preserving legacy noPull payloads. Parse source config consistently so local-only sources skip pulls and PGLite remote sources still pull.
Test: bun test test/job-pull-policy.test.ts test/autopilot-fanout.test.ts test/autopilot-fanout-wiring.test.ts test/sources-load.test.ts
* fix(auth): honor permissions.takes_holders for legacy bearer tokens over serve --http (#2529)
The OAuth provider's legacy access_tokens branch parsed permissions.source_id
but never read permissions.takes_holders, so the /mcp dispatch site's
fail-closed default pinned every remote caller to world-only takes visibility
— set-takes-holders was a silent no-op over serve --http, in both directions
(grants above world never applied; restrictions below world didn't either).
- src/core/legacy-token-scope.ts: new parseTakesHoldersAllowList shared by
BOTH transports (the drift between the legacy HTTP transport's correct
inline parse and the OAuth provider is how this bug shipped). [] preserved
as explicit deny-all; non-array → undefined → consumer defaults ['world'].
- src/core/operations.ts: AuthInfo.takesHoldersAllowList typed field
(same ride-along as sourceId/allowedSources).
- src/core/oauth-provider.ts: legacy branch threads the stored grant.
OAuth-client tokens unchanged (no per-client storage — TODO filed).
- src/mcp/http-transport.ts: converged on the shared helper (behavior no-op).
- src/commands/serve-http.ts: sidecar cast replaced by the typed field.
- src/core/facts/meta-hook.ts: hashAllowList gives [] its own cache key
(cache identity only — payload filtering stays visibility-based).
Tests: 7 verifyAccessToken cases (grant/absent/garbage/deny-all/mixed/
oauth-client/column-default), pure-helper describe, meta-hook cache-key pin,
and a Postgres e2e (test/e2e/serve-http-takes-holders.test.ts) pinning the
issue repro end-to-end over POST /mcp — the seam that had no coverage.
Reported by @Derek95king.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agent-voice): default-deny CORS + origin gate + loopback bind in reference server (#2477)
The recipe reference server reflected any Origin into
Access-Control-Allow-Origin on every route, including the unauthenticated
side-effectful POSTs. Recipe is install_kind: copy-into-host-repo, so the
reference is the source of truth for every future install.
- Default-deny CORS: headers emitted only for exact matches against the new
AGENT_VOICE_CORS_ORIGIN allowlist (comma-separated), with Vary: Origin;
Allow-Credentials never set. Same-origin /call needs no configuration.
- Origin gate on /session and /tool: CORS headers gate response reads, not
request sends — a no-preflight "simple" cross-origin POST still executes.
Disallowed Origins now 403 before any body read / upstream fetch / tool
dispatch. No-Origin callers (curl, Twilio, native) and same-origin pages
(Origin host == Host, tunnels included) pass. DNS rebinding stays a
documented production-checklist item (TODO filed).
- Loopback-default bind: HOST env, default 127.0.0.1 (mirrors gbrain
serve --http --bind default); HOST=0.0.0.0 for containers/LAN.
- Startup log prints the bind + CORS posture; recipe md + install manifest
bumped to 0.1.1 with the production checklist rewritten to match.
Tests: test/agent-voice-cors.serial.test.ts spawns the real server twice and
pins default-deny, allowlist echo + trimming, preflight behavior, and the
gate's ordering (evil-origin 403 vs no-origin reaching the handler).
Reported by @sebastiondev.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: agent-voice origin gate fails closed on malformed Origin (#2477)
Coverage-audit follow-up: a cross-origin POST with an unparseable Origin
header must 403 (new URL() throws → originAllowed returns false), never
fall through to the handler. A bypass here would defeat the gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: pin loopback-bind default + wire-level fail-closed takes default (#2529#2477)
Pre-landing review (testing specialist) flagged two revert-catching gaps:
- #2477 HOST default (127.0.0.1) had no assertion — a regression to
all-interfaces would pass every test. Capture the server's startup log
and assert the loopback bind; add a HOST=0.0.0.0 override case.
- #2529 serve-http `?? ['world']` default branch was only unit-covered.
Add a 4th e2e case: a legacy token with no takes_holders key sees
world-held takes but NOT brain-held ones over POST /mcp, pinning the
fail-closed default end-to-end. Also assert the deny-all case returns a
successful (non-error) tool result so the negatives can't pass vacuously.
Verified: agent-voice 4/4, serve-http-takes-holders e2e 4/4 (real Postgres).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): shared permissions decode + collision-free hot-memory cache key (#2529)
Adversarial-review hardening on the #2529 fix:
- The two transports shared parseTakesHoldersAllowList but still decoded the
permissions column differently: the OAuth provider JSON.parse'd a
string-typed value, the legacy HTTP transport didn't. On a double-encoded
jsonb string scalar (#2339 class) a deny-all token would fail open to
['world'] on the HTTP transport while the provider honored it. Extract
coerceLegacyPermissions into the shared module and route both through it, so
"the two transports cannot drift" is literally true (shared decode + shared
parse). Arrays/scalars/malformed strings → undefined (no grant).
- hashAllowList used bare sentinels ('_' for undefined, '(empty)' for []),
which collided with real holder values ['_'] and ['(empty)']. Encode
collision-free (undefined → 'none', else JSON.stringify(sorted)) so the
[]-vs-undefined cache separation the #2529 change relies on holds for every
holder value.
Tests: coerceLegacyPermissions unit cases (object/JSON-string/malformed/
array/scalar), all existing takes-holders + meta-hook + e2e suites green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* v0.42.74.0 fix(security): honor takes_holders over serve --http + agent-voice default-deny CORS (#2529#2477)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: document agent-voice HOST + AGENT_VOICE_CORS_ORIGIN env vars in install hint (v0.42.74.0)
The post-install hint's env-var quick-start predated the #2477 hardening and
listed neither the loopback-default HOST bind nor the default-deny
AGENT_VOICE_CORS_ORIGIN allowlist. Add both as optional entries (safe by
default) and refresh the stale startup-log line to match the server's actual
loopback-bind output.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: correct CHANGELOG command guidance + TODOS filing version (#2529#2477)
Ship-stage document-release caught two wrong CLI invocations in the v0.42.74.0
CHANGELOG "To take advantage" block: `gbrain auth permissions <token>` has no
read-only view form (that shape errors + exits 1 — set the scope directly with
`set-takes-holders <values>`), and `integrations install agent-voice --refresh`
requires `--target <host-repo>`. Also correct the follow-ups TODO header from
the plan's stale v0.42.56.0 guess to the actual ship version v0.42.74.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(deps): bump js-yaml to 3.15.1 — osv GHSA-5p4m-2wfm-xmqj (#2529#2477)
osv-scan flagged js-yaml@3.15.0 (High, CVSS 7.5), fixed in 3.15.1. The
transitive copy (gray-matter → js-yaml) was pinned to ^3.15.0 by the
package.json `overrides` block; bump both the direct dependency and the
override to ^3.15.1 so the vulnerable version is gone from bun.lock entirely
(gray-matter/js-yaml now resolves to 3.15.1). Patch bump, in-range, frontmatter
parsing verified (markdown + frontmatter + import + oauth suites green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(deps): bump admin nanoid to 3.3.18 — osv GHSA-2v37-7h3g-55p8 (#2529#2477)
osv-scan flagged nanoid@3.3.16 (High, CVSS 8.2) in admin/bun.lock, fixed in
3.3.17. nanoid is transitive (postcss → nanoid), so pin it in the admin
overrides block; refresh resolves to 3.3.18 (latest patched 3.x). Admin SPA
build verified green; both root and admin lockfiles now scan clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(security): fence the dedup-resolved slug under the caller's own confinement
put_page's resolved-slug re-check tested `ctx.auth.boundSlugPrefixes` only.
The delegated submit_agent -> subagent context carries `viaSubagent` +
`allowedSlugPrefixes` but no `auth`, so a slug-bound client holding `agent`
scope could delegate a write and have importFromContent's dedup pre-check
redirect it onto a page outside its grant — where the disk write-through
then re-rendered the victim's file with the caller's provenance.
The re-check now applies whichever confinement the caller is actually
under (OAuth binding and/or subagent allow-list / legacy namespace) via
`slugOutsideCallerFence`, which composes the existing match rules rather
than re-deriving them. Dedup returns status 'skipped' before any DB write,
so the throw still rolls nothing back. The denial does not name the
resolved slug (slug-enumeration oracle) and reads "your write scope",
since either confinement can trigger it.
Reported privately by Aleksei Razsadin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: coverage for the OAuth in-fence redirect and the missing-subagentId guard
* v0.42.73.2 fix(security): fence dedup-resolved writes to the caller's own write scope
VERSION + package.json + CHANGELOG for 0.42.73.2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: state that the write fence follows a delegated write
---------
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Registering an OAuth client with --bound-slug-prefixes now makes the write boundary real: writes outside the bound prefixes are refused on every op that can name a page, and ops that write by something other than a slug are refused outright rather than left unfenced. Deny-by-default at dispatch, so a write op added later is refused to bound clients until it is explicitly fenced.
Adds docs/integrations/qm-harness.md (gbrain as the company brain for a qm deployment) with a roster-driven provisioning script and deployment templates, plus a Known limitations section stating plainly that this is a write boundary and not a privacy boundary.
Five review rounds, including three clean-room passes by codex gpt-5.6-sol and Claude Fable 5 against an instruction-stripped tree.
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar.
Applying a schema mutation batch was not atomic: a failure partway through left earlier mutations permanently written. Reproduced on disk — a failure at index 2 left mutation 0 applied with no way to tell from the pack's state that it was half-done. The fix validates the whole batch first and writes once, which makes partial application impossible by construction rather than by careful ordering.
Verified before merge: the failure was reproduced by injecting one rather than reasoning about it; the PR's own tests fail when the fix is reverted; typecheck clean; MERGEABLE/CLEAN at 22/22 on the current base after batches 1-4 landed.
Sequenced last deliberately — it collides with #3531 on docs/architecture/KEY_FILES.md and with #3667 on src/core/operations.ts, both of which landed earlier today.
Known gap, recorded rather than hidden: lock contention under concurrent writers was reasoned about, not stress-tested.
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar.
voyage-4-large was billed at the voyage-3-large rate — $0.18 against a published $0.12 — so every cost estimate using it was wrong by 50%. Corrected in the canonical table only, per CLAUDE.md's rule that every other pricing table is a derived view, and the drift guard passes. Rate checked against the live vendor page.
Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN with 22/22 checks on the current base after batches 1-3 landed.
Known gap, recorded rather than hidden: this PR previously failed the JSONB parity guard on a 32-commit-stale base. I rebased it onto current master and re-ran rather than accepting 'flaky' — the guard passes on the real base, 22/22 green.
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar.
The back-link validator compared bare slugs, so a same-slug page in another source masked a genuinely missing reverse edge — silent under-reporting in exactly the multi-source setup where it matters. Now keyed on the full 4-tuple, per the `(source_id, slug)` uniqueness invariant. Verified on real Docker Postgres with 28/28 parity, which the PR itself had skipped.
Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN on the current base after batches 1 and 2 landed, not a stale one.
Known gap, recorded rather than hidden: remote MCP serialization of the additive Link fields is untested; the fields are additive JSON.
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar.
dashscope and google keys now fold into the gateway env — verified live end-to-end — and the retired gemini-1.5-pro default is swept from 9 files. Reverting the change fails 11 of 98 tests at fixed seams, and the budget-cap claim in the description reproduced. Landing first in the gateway/config-key seam, so #3648 rebases onto it.
Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN on the current base after batch 1 landed, not a stale one.
Known gap, recorded rather than hidden: no live provider call was made; the gemini retirement was taken from issue history rather than a vendor check. Minor follow-up to file: deriveEnvKey('google_api_key') yields a dead GOOGLE_API_KEY in the minion shell-inherit path.
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain.
`GBRAIN_FTS_LANGUAGE` was absent from the query-cache key, so a language switch served stale pre-switch rows. The hash now folds it (v14→15) with all five pin sites updated; reverting the fix fails 4 of 15 tests at the exact claimed step. Landing first in the knobs_hash cluster — the constant is single-writer, so #3617 rebases onto this and takes 16.
Verified before merge: the PR's own tests fail when the production change is reverted (11 of the previous 32 PRs failed exactly there — one had 7 of 8 new tests passing on master); typecheck clean; MERGEABLE/CLEAN with 22/22 checks green on the current base, not a stale one.
Adversarial review: survived two independent refuters — the only PR of 32 reviewed this way to do so.
The bug: `gbrain reindex-frontmatter` and `gbrain backfill <kind>` were 100% dead on PGLite. cli.ts takes the data-dir lock, the command modules built a second engine on the same dir, and acquireLock never reaps a live PID — 30s timeout, exit 1, with the error naming the waiting process itself as the holder. Reproduced on the parent commit at 33.2s; passes in 5.0s with the fix. Root-cause fix at the dispatch layer, not a softening of the lock, and the sibling census confirmed these were the only two affected callers.
Postgres path verified before merge (it was the review's one open gap, since the bug is PGLite-only and all verification had gone there while the change itself is connection-teardown ownership). Against real Postgres 16 + pgvector: reindex-frontmatter and all three registered backfills exit 0 with zero residual connections, zero advisory locks, and zero cycle-lock rows — byte-identical output and identical teardown to master on the same database, confirming the change is behavior-neutral there.
Merged tree re-verified after rebase: typecheck clean, pglite-lock + reindex-frontmatter 16 pass, llms bundle fresh, 23/23 CI green.
* docs: design engine dynamic-import reconciliation
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): reconcile dynamic import hardening
Co-Authored-By: Claude <noreply@anthropic.com>
* test(engine): guard dynamic import policy
* docs: plan engine dynamic-import reconciliation
Record the approved TDD sequence for selective engine-path hardening,
repository guard wiring, documentation, and local verification. Preserve
the no-version-bump and no-publication boundaries for the remaining work.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(engine): record static import invariant
* fix(engine): parse block comments in import guard
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): parse dynamic imports with TypeScript
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): close import guard bypasses
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): close parser guard edge cases
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): aggregate parser diagnostics
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): bound dynamic import marker directive
Require the line-level opt-out marker to be standalone inside real comment trivia so negated or incidental longer tokens cannot authorize an import. Preserve the existing general marked-line contract and pin it with focused regression coverage.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(engine): close Unicode marker boundary bypasses
Treat Unicode identifier continuations as marker-token characters and inspect adjacent text by code point so supplementary-plane characters cannot turn longer comment tokens into approvals.\n\nCo-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* v0.42.67.0 fix(build): force LF for shell scripts and route package.json checks through bash
Two independent defects left `bun run test`, `verify`, `ci:local` and
`test:e2e` dead on Windows. All four dispatch through bash.
First, every tracked *.sh is checked out with CRLF. The committed blobs are
clean LF; system-level core.autocrlf=true rewrites them on checkout, and a
strict bash then dies at run-unit-parallel.sh line 23 with
"$'\r': command not found". A root .gitattributes pinning `*.sh text eol=lf`
overrides autocrlf regardless of the contributor's git config.
Second, 33 package.json scripts invoked `scripts/foo.sh` directly, which bun
cannot exec via shebang on Windows. They now go through `bash`, matching the
11 that already did; all 59 tracked *.sh files are bash-shebanged (52
`#!/usr/bin/env bash`, 7 `#!/bin/bash`), so the change is uniform. The five
`scripts/*.ts` entries still run under bun.
Measured on this base, `bun run verify` goes from pass=1 fail=31 to pass=25
fail=7. Every one of the baseline's 29 `command not found: scripts/...`
errors is gone; those were the shebang defect, and they account for the
measured delta.
The line-ending defect is verified structurally rather than by that number,
because the bash on PATH for this measurement tolerates CR and so cannot
exhibit it: under the new attribute all 59 tracked *.sh check out LF-only
(0/59 carry a CR byte, against 59/59 before), and `git add --renormalize .`
is a no-op, confirming the index was always correct and only the working
tree was wrong. Zero content churn.
All 7 residual failures also fail on the pristine baseline: four exceed the
harness's 120s cap (standalone `bun run typecheck` exits 0), and check:wasm,
check:skill-brain-first and check:resolver are pre-existing content or
environment issues. check:resolver is not even a shell script.
No behavior change on Linux or macOS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: sync docs to v0.42.67.0
CONTRIBUTING.md gains a Windows section: the `.gitattributes` LF pin makes a
fresh clone correct with no extra steps, working copies cloned earlier need a
one-time `git rm --cached -r . -q && git reset --hard`, and new shell-script
checks must be registered as `bash scripts/<name>.sh`.
docs/TESTING.md records the shell-dispatch convention alongside the command-tier
table, and notes that the table's wallclock figures are Mac numbers: on Windows
`check:privacy`, `check:test-names`, `check:test-isolation` and `typecheck` can
exceed run-verify-parallel.sh's 120s per-check cap while passing on Linux and
macOS. It also flags that the Cygwin bash shipped with Git for Windows tolerates
CRLF where a strict bash does not, so a green local run is not evidence that a
script is CRLF-clean.
CHANGELOG.md's itemized list covers both doc updates.
`bun run build:llms` regenerates byte-identical bundles: docs/TESTING.md is
linked rather than inlined, so llms.txt / llms-full.txt do not move.
`bun test test/build-llms.test.ts` passes 12/12.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(extract): quarantine lane for auto-extracted entities from untrusted input (#160)
extractAndEnrich regex-extracts entity names from arbitrary ingested text
and creates people/ + companies/ stub pages. Those writes are now trust-
gated end to end:
- src/core/extraction-review.ts: new marker module (sibling of
quarantine.ts / embed-skip.ts, frontmatter-key pattern, no migration).
Untrusted-input stubs carry `provenance: auto-extracted` +
`status: unverified`; the shared unverifiedExtractionFragment() is the
single SQL source of truth for every consumer.
- enrichment-service: enrichEntity/enrichEntities/extractAndEnrich take
EnrichmentTrustOptions; only an explicit trusted:true writes
authoritative pages (fail-closed, mirrors the OperationContext.remote
invariant). Also threads sourceId through the write path.
- retrieval: unverified stubs rank as ordinary content — skipped by the
compiled-truth fusion boost (stampUnverifiedExtractions pre-fusion on
all three hybrid paths + keyword-only opt-out) and by the people//
companies/ namespace source-boost (guard inside buildSourceFactorCase,
shared by both engines' search SQL). Results carry `unverified: true`.
New engine method getUnverifiedExtractionPageIds in BOTH engines.
- ops (contract-first): extract_entities (direct write only for
ctx.remote === false + --trusted-extraction; everything else
quarantines), extraction_pending (read, source-scoped list),
extraction_review (owner-only batch promote/reject; promote flips
status to verified keeping provenance for audit, reject soft-deletes).
- doctor: unverified_extractions check warns on stubs older than N days
(default 7) with the exact review commands.
Tests: test/extraction-review.test.ts (PGLite: fail-closed matrix incl.
remote-unset, fusion boost skip, review queue, doctor, hostile-transcript
e2e proving fake entities land quarantined and rank below a verified page
of equal lexical relevance) + test/e2e/extraction-review-postgres.test.ts
(live Postgres parity, verified against pgvector:pg16). sql-ranking
expectations updated to current state. Docs: KEY_FILES + RETRIEVAL +
llms rebuild.
Closes#160
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(extract): close vector-arm source-boost gap + harden extract_entities (#160 review round)
Adversarial review of the quarantine lane found the people//companies/
1.2x source factor still applied to unverified stubs inside searchVector's
pre-LIMIT re-rank (a different multiplier from the fusion-level 2.0x the
lane already cancels — and applied early enough to evict legitimate pages
from the candidate pool, which nothing downstream can restore).
- buildSourceFactorCase gains an optional unverifiedGuardColumn for the
bare-slug re-rank form; both engines' hnsw_candidates CTEs now project
the guard predicate as `unverified_stub` and the factor CASE checks it
first. Wrong "fusion covers the vector arm" comment corrected.
- extract_entities resource guards: 200k-char input cap (loud reject),
200-entity cap surfaced as `truncated` + `entities_found`; the library
extractAndEnrich gets the same default cap. (OperationContext has no
abort signal field — caps are the bound.)
- extraction_review promote is now a targeted JSONB-merge UPDATE instead
of putPage, so non-carried columns (page_kind, content_hash) can't be
reset by the upsert.
- extraction_pending applies buildVisibilityClause (archived-source stubs
no longer list).
- Wording: op description + module header now state the marker-strip
assumption plainly (markers are ordinary frontmatter; the boundary
against wholesale rewrite is put_page write authz) and document the
CREATE-only scope of the lane.
Tests: vector-arm factor-1.0 pinned on BOTH engines (PGLite unit + live
Postgres e2e, identical basis embeddings → score ratio is the factor);
resource-guard test (oversize reject + 300-entity flood capped at 200);
guard-column form pinned in the buildSourceFactorCase unit test.
search/ suite (340), sql-ranking, searchvector-maxpool, title-retrieval-
arm, rrf-source-key, doctor, ops, cli suites all green; JSONB guards clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(migrate): provider-agnostic embedding migration service — the path off ZeroEntropy (#3390)
- gbrain migrate embeddings --to <provider:model> (alias: retrieval-upgrade):
plan + cost preflight, consent gate (--yes / TTY confirm / non-TTY exit 2),
live probe against the target provider before any mutation, env-override
gate, schema dimension transition via the shared runSchemaTransition,
dual-plane config write, NULL-signature-inclusive invalidation, query-cache
purge, resumable re-embed through the standard embed pipeline (single-flight
locks, backoff, pacing, stderr progress). Killed runs resume by re-running
the same command; the NULL-embedding column is the checkpoint.
- #3391 root-cause fix (both engines): countStaleChunks / sumStaleChunkChars /
invalidateStaleSignatureEmbeddings accept includeNullSignature to lift the
v108 grandfather clause; embed --stale warns loudly when a model swap
leaves NULL-signature pages in the old embedding space, and
--include-null-signature re-embeds them. Default sweep behavior unchanged.
- knobs_hash v=12 → v=13 (prov=default legacy callers must not be served
pre-migration cache rows).
- migrate_embeddings op: scope admin, localOnly, hidden cliHints, hard
remote refusal, needs_confirmation without yes=true.
- One-shot post-upgrade ZE-sunset banner (ze_sunset_notice_shown) for brains
resolving to a zeroentropyai:* embedding model or reranker.
- doctor's dimension-mismatch repair hint now names the real command.
- Docs: docs/guides/embedding-migration.md, KEY_FILES entries, spend-controls
gate row. Tests: PGLite unit + full-lifecycle flow (interrupted-run resume),
real-Postgres e2e (pgvector DDL path + #3391 predicate parity).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(test): satisfy check:test-isolation + bump the remaining knobs_hash pins
- test/migrate-embeddings-flow.test.ts → .serial.test.ts: the file holds a
temp GBRAIN_HOME + an installed fake embed transport for its whole
lifecycle (beforeAll → afterAll), which withEnv() can't wrap. This also
fixes the CI shard-pollution failure in
test/ai/recipes-existing-regression.test.ts (that file passes solo on both
master and this branch; the flow test's configureGateway + provider-key
deletion was leaking into it inside the same shard process).
- test/embedding-migration.test.ts: env-override case now uses withEnv().
- Bump the three remaining KNOBS_HASH_VERSION pins to 13
(cross-modal-phase1, search-alias-resolved-boost, search/knobs-hash-reranker).
- Docs + llms bundles follow the test rename.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(test): wire the new Postgres e2e into the smart e2e selector map
Changes to embed.ts / embedding-migration.ts / retrieval-upgrade-planner.ts /
postgres-engine.ts now trigger test/e2e/migrate-embeddings-postgres.test.ts —
the #3391 stale predicates and runSchemaTransition's DDL path behave
differently on real pgvector than on PGLite, so the smart selector has to know.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(migrate): consult spend.posture in the embedding-migration consent gate
The brief asked the gate to honor spend.posture; it previously didn't read it
at all. Now it does — but deliberately does NOT bypass on tokenmax: posture
waives the spend CEILING, and this gate also guards a destructive schema
rebuild (existing vectors dropped, retrieval degraded until the re-embed
finishes). Under tokenmax the dollar figure is marked informational on stderr
and the confirmation is still asked; --yes stays the single scripted bypass.
Pinned by a new case in the flow test so a later refactor can't quietly turn
posture into a bypass. Guide + spend-controls table updated to match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* wip: blocker fixes
---------
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: support OpenRouter API key in config
* fixup: dedupe openrouter_api_key vs master, drop no-op compile-guard test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
sources.local_path is machine-specific state in a brain-wide table. Any
brain whose sources were registered from more than one machine — or a
sanctioned setup mid-migration (topologies.md Topology 2, or the
system-of-record git flow before every repo is cloned) — has sources
whose checkout is not present on the machine running sync --all. Each
surfaced as a hard failure and forced rc=1 every run; on one observed
fleet that was 12 phantom failures per hour, training operators to
ignore the exit code.
--missing-path skip classifies them honestly: ⊘ in the human aggregate,
status skipped_missing_path + local_path in the --json envelope, new
skipped_count, excluded from error_count and the rc=1 gate. Using the
flag outside --all warns instead of silently no-oping.
Default stays fail: on a single-machine brain a missing local_path
usually means an unmounted volume or deleted checkout, and silently
skipping would hide data loss. Skip is explicit opt-in.
Pure helpers (parseMissingPathMode, partitionMissingPathSources)
exported and unit-tested in the sync-all-parallel style — no DB, no fs.
Docs: sync --help, docs/TESTING.md inventory, KEY_FILES.md sync entry.
CHANGELOG/VERSION deliberately untouched per the release process.
Co-authored-by: Ziggy <lazyclaw137@gmail.com>
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Lazydayz137 <Lazydayz137@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(sources): recover corrupted config shapes (#3401)
Use one canonical normalizer for nested string and array-shaped source configs across federation reads, config writes, archive/restore, and doctor remediation.\n\nFixes #3401\nFixes #3402\nFixes #3403
Signed-off-by: arisgysel-design <arisgysel-design@users.noreply.github.com>
* fix(sources): bind restoreSource federated patch via ::text::jsonb (#2339 class)
restoreSource bound a JS JSON string to a bare $1::jsonb placeholder;
postgres.js double-encodes that into a jsonb string scalar, so on the
Postgres engine the coerced object || string-scalar concat evaluates as
array-concat and restore RE-CORRUPTS the exact config shape this PR
repairs. PGLite masks the bug (its driver parses the bind natively).
Fix: bind through $1::text::jsonb per the repo JSONB rule.
Adds the DATABASE_URL-gated Postgres regression
(test/e2e/restore-source-config-jsonb-postgres.test.ts): seeds a
corrupted string-scalar config, runs archive -> restore, asserts
jsonb_typeof(config) = 'object' with the federated flag applied and
pre-existing keys preserved. Verified red on the bare ::jsonb bind
(config became a jsonb array) and green on the fix against a real
pgvector Postgres; skips cleanly without DATABASE_URL.
Co-authored-by: arisgysel-design <arisgysel-design@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Signed-off-by: arisgysel-design <arisgysel-design@users.noreply.github.com>
Co-authored-by: arisgysel-design <arisgysel-design@users.noreply.github.com>
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Anthropic released Claude Opus 5, at the same $5/$25 pricing tier as
Opus 4.8. Neither the chat recipe allowlist nor CANONICAL_PRICING knew
about it, so operators could not opt into it via models.tier.deep /
models.default without gbrain rejecting the id.
- src/core/ai/recipes/anthropic.ts: add claude-opus-5 to the models list.
- src/core/model-pricing.ts: add anthropic:claude-opus-5 { input: 5.00,
output: 25.00 } (plus cache rates, matching Opus 4.8's ratios).
- src/core/takes-quality-eval/pricing.ts: add it to SUPPORTED_MODELS so
eval takes-quality run --budget-usd doesn't reject it during preflight.
- Refreshed the stale pricing-verification date and the Opus list in
docs/architecture/KEY_FILES.md.
- Tests: pinned-value regression in test/model-pricing.test.ts, recipe
membership in test/anthropic-model-ids.test.ts, budget-pricing coverage
in test/eval-takes-quality-pricing.test.ts.
Scope: registration only. TIER_DEFAULTS / DEFAULT_ALIASES /
DEFAULT_CHAT_MODEL are untouched — default-routing bumps are the
separate, already-open #2858; this just makes the id valid/priced for
operators who opt in explicitly.
* v0.42.66.0 fix(extract): make conversation backfill outcomes durable (takeover of #3293)
Versioned, snapshot-bound terminal audit rows become the durable authority
for conversation fact backfill completion; checkpoint GC can no longer
repeat completed model work, and best-effort empty results no longer mask
provider/output failures as complete.
Supersedes #3293 (rebased onto current master; only version-trio conflicts).
Co-authored-by: FloridaStyle <danwiggins@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: drop version-trio bump — individual fixes do not carry version bumps (release PRs do)
* merge: reconcile durable-outcome skip accounting with master's LLM fallback tests
The two fallback replay tests from #3371 asserted the legacy checkpoint
pages_skipped counter; under this PR's durable-outcome authority a
completed page is skipped via pages_skipped_completed before any parse.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: FloridaStyle <danwiggins@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds the bold-time-dash built-in pattern: **Speaker** HH:MM <dash> text
(em dash, en dash, or ASCII hyphen), valid 24-hour times only, date from
page frontmatter/date headings, multi-line continuation bodies.
Opt-in score_continuations_as_body scoring keeps long multiline messages
parseable while preserving the sparse-prose false-positive floor (needs
two anchors or a first-line anchor before candidate-only scoring kicks in).
Hardens validatePatternEntry to reject non-integer / out-of-range capture
indexes including text_group. Adds maintainer doc, JSONL fixtures, and
adversarial coverage.
Takeover of #3289 (fork branch went CONFLICTING against master on the
version trio); code applied 3-way, version/CHANGELOG bump dropped per
fleet release convention.
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: FloridaStyle <daniel.wiggins@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
OAuth/source scoping only guards the serve --http path; a container
sharing Docker's default bridge with the brain's Postgres can open a
direct DB session without a token. Adds a 'Co-located Docker workloads'
subsection to docs/mcp/DEPLOY.md with the operator checklist, a
trust-boundary paragraph in SECURITY.md, and an ops note + cross-link
in the company-brain tutorial.
Fixes#3270
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Rebase of #3279 onto current master: the whoami oauth shape gains
source_id (AuthInfo.sourceId, null when absent) and federated_read
(AuthInfo.allowedSources, [] when absent) — read-only self-introspection
that widens no grant. Re-applied against the post-#3091 description
string (stdio transport shape preserved) and merged the grant tests
into the current whoami.test.ts alongside the stdio cases.
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: boundless-forest <boundless-forest@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(jobs/autopilot): interval persistence, --lock-duration flag, dead-jobs doctor check, deployment-shape docs
Four backlog items in the jobs/autopilot workers, locks & installers area:
- #2794: `gbrain autopilot --install` silently dropped `--interval`. The
installer now parses + validates it, persists it to config
(autopilot.interval), and threads it into the wrapper's exec line; a
later flag-less --install regenerates the wrapper from the persisted
value, and the daemon run path falls back to the same config key.
- #1014: new `--lock-duration MS` flag (env: GBRAIN_LOCK_DURATION) on
`gbrain jobs work` and `gbrain jobs supervisor` to tune the worker
stall-lock window (and so the lockDuration x max_stalled wall-clock
dead-letter cap). Validated like --health-interval (integer >= 1000ms);
the supervisor propagates it to the spawned worker via buildWorkerArgs;
shown in the worker startup banner.
- Takeover of PR #1185 (@ethanbeard): `gbrain integrations doctor` now
surfaces dead minion jobs as a cross-cutting [queue] check. Reworked
from the original: consumes a new machine-readable `gbrain jobs list
--json` surface instead of screen-scraping the human table (long job
names shift the columns), and scopes to a 24h finished_at window so one
ancient dead job can't flag ISSUES forever (parity with main doctor's
queue checks).
- #631: documented the production deployment shape for autopilot vs jobs
supervisor in docs/guides/minions-deployment.md — recommend the
`autopilot --no-worker` + `jobs supervisor` split, warn against running
both worker lanes, and cross-link the --no-worker liveness probe.
Co-authored-by: ethanbeard <ethanbeard@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(autopilot): make #2794 wrapper-script tests hermetic — fake gbrain on PATH
writeWrapperScript calls resolveGbrainCliPath(), which shells out to
`which gbrain` and throws on CI runners where no gbrain binary is
installed. The two new --interval threading tests failed only in CI
(dev machines have gbrain on PATH). Prepend a fake executable to PATH
for the describe block so resolution is deterministic everywhere.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: ethanbeard <ethanbeard@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(sync): report partial (pull_failed) instead of up_to_date when git pull fails with zero imports (#3068)
A warn-and-continue internal git pull failure (e.g. a local-path origin
rejected by protocol.file.allow=never) combined with a zero-import run
previously reported `up_to_date`, exited 0, and bumped the last_sync_at
freshness heartbeat. A permanently-failing pull was therefore invisible
forever: doctor's sync_freshness never fired and every scheduled sync
looked clean while the source silently went stale.
Now, when the pull failed and the run imported nothing, sync returns
`partial` with the new reason `pull_failed`, leaves last_commit AND
last_sync_at untouched (so staleness monitoring fires), and prints a
dedicated non-success message. The fall-through-to-working-tree design
is unchanged: local commits still import when the remote is unreachable,
and the anchor still advances over commits that were actually imported.
Addresses the report in #3068.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sync): surface pull_failed to CLI exit codes, sync --all JSON, and the cycle phase (#3068 review round)
Codex review round 1 follow-ups:
- Single-source `gbrain sync` sets exit code 1 on partial/pull_failed
(timeout-class partials keep exit 0 — they converge on retry; a failing
pull does not).
- `sync --all` exits 1 when any source reports pull_failed, and the
--json envelope carries the per-source partial `reason`.
- The autopilot cycle's sync phase maps partial/pull_failed to `warn`
with a dedicated summary and a `syncReason` detail, so a scheduled
cycle no longer reports a clean run over a wedged source.
- The regression test now isolates GBRAIN_HOME to a temp dir so the
first full sync cannot touch the real sync-failure ledger.
- Current-state docs: KEY_FILES.md sync.ts entry + TESTING.md inventory
describe the pull_failed contract and the new test.
Addresses the report in #3068.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sync): route pull_failed exit through the owned verdict channel; make the regression test serial (#3068 review round 2)
Codex review round 2 follow-ups:
- Single-source exit now uses setCliExitVerdict(1) instead of a raw
process.exitCode assignment, which the CLI teardown deliberately
ignores (PGLite's Emscripten runtime clobbers process.exitCode
mid-run; the owned channel in src/core/cli-force-exit.ts is the only
trusted verdict). Pinned by test/cli-exit-verdict-pin.test.ts.
- The regression test is renamed to *.serial.test.ts because it pins
GBRAIN_HOME for the whole file (scripts/check-test-isolation.sh R1);
docs updated to the new name.
Addresses the report in #3068.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
deriveDirectUrl() swaps the Supabase pooler host to db.<ref>.supabase.co:5432,
which is IPv6-only without the paid IPv4 add-on. On IPv4-only networks the
direct pool could never connect, and initDirectPool()'s throw killed
'gbrain init --url' and migrations with ENOTFOUND/ECONNREFUSED.
getDirectPool() now classifies network-unreachable errors (ENOTFOUND,
ECONNREFUSED, ENETUNREACH, EHOSTUNREACH, ETIMEDOUT, CONNECT_TIMEOUT) via the
new isNetworkUnreachableError(), self-activates the kill-switch, logs one
stderr line pointing at GBRAIN_DIRECT_DATABASE_URL / GBRAIN_DISABLE_DIRECT_POOL,
and returns the read pool. Auth/SQL errors still throw (misconfig, not
unreachability). The failed pool is ended via endPoolBounded so it can't
leak sockets into the now-continuing process.
Also surfaces the kill-switch + override envs in the init.ts IPv6 warnings
and docs/guides/live-sync.md (they were previously undocumented outside
connection-manager.ts).
Fixes#1641
Co-authored-by: Sinabina <sinabina@Sinabinas-MacBook-Pro-4.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(calibration): resolve owner holder via config (default 'self'), fixes#2464
Takeover of #2467 (rebased onto master). consolidate writes owner takes
with holder='self' while calibration-profile, calibration CLI/op, think's
calibration block, emotional-weight, and doctor's calibration_freshness
all defaulted to a hardcoded 'garry' — so getScorecard returned 0
resolved and the calibration profile never built on non-upstream brains.
New src/core/owner-holder.ts is the single source of truth:
resolveOwnerHolder({override, configValue}) = override >
emotional_weight.user_holder config > 'self'. All six call sites route
through it; doctor's freshness SQL is parameterized ().
Upgrade note: upstream-owner brains with historical holder='garry'
profiles should `gbrain config set emotional_weight.user_holder garry`
to keep reading them.
Co-authored-by: devty <devty@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(calibration): replace real-name holder fixture with charlie-example placeholder
Privacy iron rule: no real people's names in checked-in code. The sanctioned
placeholder mapping uses people/charlie-example.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Sinabina <sinabina@Sinabinas-MacBook-Pro-4.local>
Co-authored-by: devty <devty@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Garry Tan <garrytan@gmail.com>
Add the OpenClaw-required top-level id to openclaw.plugin.json, export a
direct register(api) entrypoint from src/openclaw-context-engine.ts, add a
manifest regression test, and document that skillpack harvest must preserve
OpenClaw-native manifest fields (id, configSchema, contracts).
llms bundles regenerated (bun run build:llms) — no content drift.
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Filip <FilipHarald@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Takeover of #2719 (fork head; rebased onto origin/master).
- writeProposed now writes both best.md (current-best pointer) and
proposed.md (stable human-review artifact); returns the proposal path.
- Orchestrator reports the real proposed.md path for accepted --no-mutate runs.
- Tutorial updated; llms bundles regenerated (no content drift — tutorial
is not inlined in the bundle).
Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>