Wave-assembled from PR #3548 by @time-attack. Composition conflicts vs wave
item #3993 resolved: EmbedResult keeps both chunkless_pages_healed and the
PR's lock_skipped; KEY_FILES.md keeps the #3993 embed entry and takes this
PR's migrate-embeddings entry.
Co-Authored-By: Garry Tan <garrytan@gmail.com>
Wave-assembled from PR #3888 by @RerankerGuo. Conflict resolution: KEY_FILES.md
check-resolvable/skill-frontmatter/skill-trigger-index entries taken as the
PR's current-state prose; the PR's stale pre-fold 'doctor.ts extension' entry
dropped (master already folded it into the main doctor entry).
Co-Authored-By: RerankerGuo <121015044+RerankerGuo@users.noreply.github.com>
Wave-assembled from PR #3936 by @dovstern. Conflict resolution: kept master's
resolveRepoRoot() block AND the PR's exported currentBranch in
src/core/brain-repo-durability.ts. Adaptation: the new serial test now writes
the simulated push log under $GBRAIN_HOME/.gbrain (CX2-8 parent-dir semantics
landed on master after the PR's base).
Co-Authored-By: Dov Stern <dovstern@users.noreply.github.com>
Wave-assembled from PR #3976 by @clement0909472. Rider: four toContain pins
(claude-fable-5, claude-opus-5, claude-opus-4-8, claude-sonnet-5) in
test/claude-cli-recipe.test.ts.
Co-Authored-By: Clément Barberousse <clement.barberousse.pro@gmail.com>
Wave-assembled from PR #3782 by @JonMcCutchen. Rider-check: verified the
non-default pageRoot join (repoPath/.sources/<sourceId>) matches how
pages.source_path is recorded (source-root-relative via importFile's
relative(dir, filePath)); no mismatch, no change needed.
Co-Authored-By: Jon McCutchen <jmmccutchen1@gmail.com>
Wave-assembled from PR #3762 by @awilhite. Rider: PGLite round-trip chunk-count
assertion in test/extract-atoms-chunk-embed.test.ts (verified fails without the
src change).
Co-Authored-By: Austin Wilhite <austinw80@gmail.com>
* fix(bootstrap): preview source_id + create brain/ eagerly in hooks phase
`bootstrap render`/`hooks` never told a human what source_id the
workspace expects until `verify` (the only engine-holding phase) ran.
A human who hand-registered a source before that point would guess an
"intuitive" name, hit an FK error on the first `verify` roundtrip (the
guessed id has no `sources` row), then hit `overlapping_path` on the
retry (their first guess still claims the same brain/ dir) — three
round trips to land the right id.
`hooks` is the last ENGINE-FREE phase before `verify`, and already
knows both the manifest's current source_id and the workspace path, so
it now:
- creates `<ws>/brain` eagerly (idempotent mkdir), removing the
manual-mkdir step before `git init && sources add`
- prints the exact `gbrain sources add <source_id> --path <brain>`
command
- previews the collision-fallback id verify would derive
(`workspace-<hash>`) — a pure function of the workspace's real
path, so it needs no DB lookup and is safe to preview engine-free
The collision-fallback derivation itself is unchanged; it is now
factored into an exported `deriveWorkspaceSourceId()` in verify.ts so
both call sites (the new hooks preview and the existing
`resolveSourceIdCollision`) share one formula instead of two copies
drifting apart.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JNV9ABwb32DZEwvo7P4ay
* fix(bootstrap): --force the preview command; quote paths; fix runbook
Codex review round 2 caught three issues with the source_id preview
added in the prior commit:
- The printed `gbrain sources add <id> --path <brain>` command failed
immediately on a pristine bootstrap: the brain/ dir this phase just
created is empty (no git history), so `sources add --path` fail-fasts
as `not_a_git_repo` (#2707). Fixed by appending `--force` — the same
sanctioned opt-in `test/bootstrap-verify.serial.test.ts` already uses
to register a brand-new brain/ before any content exists
(`addSource(engine, { id: 'workspace', localPath: ..., force: true })`).
Safe here specifically because brainDir is the fixed
`<workspace>/brain` path this phase just created, not an arbitrary
user path.
- brainDir was interpolated unquoted; a workspace path containing a
space broke the printed command. Added a local
`shellQuoteForDisplay()` (mirroring the existing private `shellQuote`
already duplicated in hooks.ts / sources-ops.ts / connect.ts).
- The dispatcher test only pattern-matched the collision-fallback id's
shape (`workspace-[0-9a-f]{8}`) instead of pinning exact equality
with `deriveWorkspaceSourceId()`, so preview/verify drift could pass
silently. Now asserts exact equality, plus a new test for the space-
quoting fix.
Also corrects BOOTSTRAP_FOR_AGENTS.md's runbook step 5, which claimed
skill scaffolding "registers `brain/` as the workspace source" — no
code path does this automatically (confirmed by grep); the step now
points at the `hooks` phase's actual preview + --force command instead
of telling the installing agent there is "nothing to judge" on a step
that silently never ran.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JNV9ABwb32DZEwvo7P4ay
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
`gbrain bootstrap <subcommand> --help` (a help token AFTER the subcommand
name, e.g. `gbrain bootstrap uninstall --help`) fell through into the
subcommand's own arg parsing instead of printing help, since none of the
mutating handlers (repo/hooks/verify/attach/uninstall/render/interview)
checked for --help/-h/help themselves. `uninstall --help` ran a real
uninstall; `repo --help` created a real private GitHub repo; etc.
Add a SUBCOMMAND_HELP usage map plus a pre-dispatch hasHelpToken() guard in
runBootstrap so a help token anywhere in the subcommand's args short-circuits
before any lock/runner/engine/handler call. Bare `help` (no dashes) is also
recognized, except for `interview` (its --set KEY value free-text answers
could legitimately be the literal word "help").
New test/bootstrap-subcommand-help.serial.test.ts arms fixtures so the real
operation would reach its side effect if the guard were removed (an
already-rendered workspace for render/hooks/attach, an operational verify
config, an isolated uninstall home with a real receipt-tracked file, a fresh
interview workspace) and asserts nothing mutates.
* fix(doctor): honor the recorded pid_file in supervisor_singleton check
`gbrain doctor`'s `supervisor_singleton` check (#1849) compares the local
pidfile holder against the queue-scoped DB lock holder. It read
`readSupervisorPid(DEFAULT_PID_FILE)` unconditionally, even though the
supervisor's own 'started' audit event already records the pid-file path
actually in use (`this.opts.pidFile`). A supervisor launched with a custom
`--pid-file` (e.g. a launchd-managed deployment) would then get a false
"singleton mismatch" warning against its own healthy, single instance,
because the pidfile doctor read was never the one the supervisor wrote.
- doctor.ts now prefers `lastStarted.pid_file` when present, falling back
to `DEFAULT_PID_FILE` for events that predate the field.
- supervisor.ts resolves `pid_file` to an absolute path at emit time (the
only cwd context in which a relative `--pid-file` is meaningful), so a
later reader running from a different cwd doesn't misresolve it. The
process's own internal pidfile guard/read/write paths are untouched.
The DB lock (`gbrain_cycle_locks`) remains the sole singleton authority per
#1849 — this only corrects which pidfile the diagnostic display reads.
Added test/doctor-supervisor-singleton-pidfile.test.ts covering the fixed
path, the still-mismatching absent-pidfile case, and a source-grep pin for
the compatibility fallback.
* docs(comments): correct pid_file fallback rationale and drop unsupported claims
The 'started' audit event has carried pid_file since the supervisor's
introduction, so the fallback comment no longer claims a predate case;
DEFAULT_PID_FILE is env-overridable (GBRAIN_SUPERVISOR_PID_FILE), so the
comments stop calling it HOME-derived; and the custom --pid-file scenario
is now described as an example rather than asserted as common.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126D3zLWL5RE3CVxnPANiiU
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#1586 threaded the cycle's resolved source through the synthesize phase so
subagent-written pages land in that source's (source_id, slug) rows. The
patterns phase was not updated and kept the pre-#1586 shape: it stamped a
literal 'default' on every collected ref and compared the reverse-write path
against a literal 'default'.
On a per-source cycle that files the page against the wrong source. The row is
created under 'default' (the child had no source_id to scope its put_page
calls) while the reverse-write drops the file into the named source's checkout,
because source_id === 'default' selects the brainDir/<slug>.md branch and
brainDir IS that source's checkout. Row and file then disagree about which
source owns the page — what doctor reports as multi_source_drift.
Applies the same threading synthesize.ts already uses: PatternsPhaseOpts gains
sourceId, cycle.ts passes cycleSourceId at the patterns call site the way it
already does for synthesize, the child carries SubagentHandlerData.source_id,
and reverseWriteRefs takes the cycle source as its native source. Unset stays
'default', so unscoped callers are unchanged.
* fix(bootstrap): Gate 2 checks only the active gh account, not every registered one
createPrivateRepo's Gate 2 ran bare `gh auth status` and treated any
non-zero exit as "not authenticated". That command aggregates every
registered account across every host and exits 1 if even one of them
has auth issues — so a stale, unused, expired account (or one on an
unrelated GitHub Enterprise host) false-blocks `gbrain bootstrap repo`
even while the actual active account works fine.
Gate 2 now scopes the check with `--hostname github.com` (this flow is
already github.com-only end to end: parseGithubOwnerRepo, the
repo-create URL fallback, etc.) and, when the installed `gh` supports
it, `--active` as well (added in cli/cli v2.57.0 — confirmed present at
v2.57.0 and absent at v2.56.0 by diffing status.go across tags on
cli/cli). Support is detected from the `gh --version` output Gate 1
already captures, so an older `gh` falls back to the host-scoped bare
form instead of hard-failing on an unrecognized flag.
Verified `gh auth status --active`'s semantics directly against
cli/cli's pkg/cmd/auth/status/status.go: passing --active skips the
per-host loop over non-active accounts entirely (`if opts.Active {
continue }`), so only the active account's entry can affect the exit
code.
* fix(bootstrap): regenerate flag registry for the new gh --active/--hostname literals
The prior commit's Gate 2 change added the string literals `--active` and
`--hostname` inside src/core/bootstrap/repo.ts (both in the gh argv and in
comments). src/commands/bootstrap.ts statically imports repo.ts, and
scripts/generate-flag-registry.ts scans one level of relative imports from
each CLI_ONLY case block to build the committed, freshness-pinned
src/core/cli-flag-registry.generated.ts (#2185) — so the 'bootstrap' entry
was stale relative to a fresh `bun run build:flag-registry` run, failing
test/cli-flag-validation.test.ts's freshness guard in CI.
Regenerated via `bun run build:flag-registry`; the only change is 'bootstrap'
gaining '--active' and '--hostname' alongside its existing ~70 entries. This
is the generator's documented, deliberately over-inclusive behavior (accepting
an unused flag is the pre-#2185 status quo) — neither flag is
security-sensitive or read from user input; both are hardcoded in the `gh`
subprocess invocation, not accepted from `gbrain bootstrap`'s own CLI args.
Confirmed via `git fetch upstream && git log HEAD..upstream/master --oneline`
(0 commits) that this is not upstream drift — purely caused by this PR's own
diff.
Fixes#3962
Return the structured extraction result for --json callers while preserving the existing human summary. Add a behavior-level regression test that proves stdout is parseable JSON.
* fix(facts): make transcript pages facts-extraction eligible
`gbrain extract-conversation-facts`'s ALLOWED_TYPES allowlist omitted the
`transcript` page type, so gbrain's own nightly transcript-ingest pages
were silently skipped by both the CLI `--types` validation and the
`cycle.conversation_facts_backfill.types` config filter. Even with the
type allowed, the built-in conversation-parser had no pattern for the
`## User` / `## Assistant` markdown-heading turn shape that transcript
ingest writes into `compiled_truth`, so parsing would still yield 0
segments.
This PR makes an explicit decision: transcript pages ARE now
facts-extraction eligible. That is a real behavioral change (a new,
potentially large corpus starts flowing through the extraction +
segment-cost path), not a no-op bugfix — flagging it plainly rather than
padding out the change as narrower than it is.
Changes:
- `src/commands/extract-conversation-facts.ts`: add `'transcript'` to
`ALLOWED_TYPES` / `ALLOWED_TYPE_ALIASES` (the single source of truth
for this allowlist).
- `src/core/conversation-parser/builtins.ts`: add the `markdown-heading-turn`
builtin pattern recognizing heading-only `## User` / `## Assistant` /
`## Human` / `## System` lines as turn openers, with D5 continuation-line
body absorption. `quick_reject` is deliberately scoped to the role-prefix
(not a bare `#{2,3}` heading check) so a message body that happens to
paste unrelated markdown headings doesn't starve the D18 scorer's
anchor-candidate ratio.
- `src/commands/jobs.ts`, `src/commands/doctor.ts` (x2 checks),
`src/commands/sources.ts`: these each carried their own hand-copied
literal of the same allowed-types list (background-job type filter,
`conversation_facts_backlog` doctor check, `conversation_format_coverage`
doctor check, `facts_backfill_estimate`). Switched each to import
`ALLOWED_TYPES` from the command module instead of re-listing it, so this
class of drift (a type added in one place, silently excluded everywhere
else) can't recur.
- `docs/architecture/KEY_FILES.md`: updated the two stale mentions (pattern
count 17→18, allowlist list) to current-state per this repo's own
reference-doc convention.
Known limitation (not fixed here, scope-bounded intentionally): parsing is
context-free, same as every other multi-line builtin in this registry — a
message body that contains a literal `## User` line (e.g. someone pasting
a markdown transcript excerpt into their own message) would be read as a
turn boundary. This is a pre-existing property of the whole parser
(`applyPattern`'s per-line scan has no fence-awareness), not something
this PR introduces or could fix without a much larger, separate change to
the shared orchestrator affecting all 18 patterns. Flagging it here rather
than silently shipping the same limitation as the other 17 builtins.
Tests: 4 new tests (2 in test/extract-conversation-facts.test.ts, 2 in
test/conversation-parser/parse.test.ts) covering the allowlist, the new
pattern's positive match + continuation absorption, and that ordinary
`## Summary`-style headings are correctly rejected. Full targeted suite
(conversation-parser + facts-extraction + doctor backlog + build-llms
freshness): 263 pass / 0 fail. typecheck clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126D3zLWL5RE3CVxnPANiiU
* fix(facts): read the type allowlist from core, not the command module
CI caught this: the known-flags registry drifted for doctor, sources, and
repos. The obvious remedy the guard prints -- regenerate and commit -- would
have been a regression, so this takes the other route.
The generator walks one level of a command module's relative imports and
harvests every flag-shaped string it finds, help text included, and is
deliberately over-inclusive. Importing extract-conversation-facts.ts just to
read ALLOWED_TYPES therefore spliced that command's entire flag vocabulary
(--types, --sleep, --slug, --segment-limit, --override-disabled, ...) into
the allowlists of three commands that implement none of it: `gbrain doctor
--types foo` would have passed validation and been silently ignored. That is
the exact defect class #2185 exists to close.
(jobs.ts is unaffected: it already imported the command module on one line
for runExtractConversationFactsCore, so those flags were already in its
registry entry before this branch.)
ALLOWED_TYPES + ALLOWED_TYPE_ALIASES now live in
src/core/conversation-facts-types.ts, a constants-only module with no CLI
text to harvest. extract-conversation-facts.ts re-exports both so its
existing importers are unchanged.
Verified: registry regenerates to zero drift (was doctor/repos/sources),
cli-flag-validation 24 pass, typecheck clean, 287 pass across the touched
areas. Confirmed against a clean upstream/master worktree that the drift was
introduced by this branch and is not pre-existing.
* fix(conversation-parser): reduce to the parser pattern only
Withdraws the `transcript` allowlist half of this branch. The premise was
wrong: `transcript` is not an upstream page type. `ALL_PAGE_TYPES` does not
contain it, `gbrain-base.yaml` declares `conversation` for "long-running
chat/transcript pages" and marks it `extractable: true` precisely so
extract-conversation-facts walks it, and `gbrain-base-v2.yaml` lists
`transcript` as an alias of `source` (a media primitive). Pages typed
`transcript` are a convention of my own ingest pipeline, not something
upstream produces — the fix for that belongs on my side, by emitting
`conversation`.
That takes the four call-site de-duplications with it (they existed only to
keep the allowlist in sync), and with them the flag-registry drift: no
imports are added, so the registry regenerates to zero drift with no
constants module needed.
What remains is the half that stands on its own: a `conversation` page whose
body uses `## User` / `## Assistant` headings matches none of the 17 builtins
and parses to 0 segments. `markdown-heading-turn` is an 18th pattern in the
same shape as the iMessage/Circleback additions before it.
Verified: typecheck clean, 181 pass / 0 fail across the parser, extraction,
flag-registry and llms-freshness suites, registry drift zero.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(skills): company-brainify — close three sanitization-defeating defects
1. Scope truncation: the first structural grep used '>' and overwrote the
retrieval-discovered scope list; Phase 1 now writes retrieval paths to
/tmp/brainify-scope.txt explicitly and both greps append.
2. Facts reconciliation: the skill claimed 'gbrain sync' makes the DB stop
serving deleted Facts-fence rows. Sync's convergence contract covers page
import only — fact extraction is explicitly decoupled (src/commands/sync.ts
CONVERGENCE CONTRACT); the reconcile lives in the extract-facts sweep
(src/core/cycle/extract-facts.ts). The procedure now triggers the sweep and
verifies removal with 'gbrain recall --grep' before certifying.
3. Backup retention glob: the backup is created as
shared-brain-history-backup-<ts>.git but cleanup documented
brain-history-backup-<date>.git — a pattern that matches nothing, silently
retaining the pre-sanitization history mirror forever. Globs now agree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(skills): regenerate skills.lock.json after master fix-wave
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): citation-graph-ingest — check-backlinks requires a subcommand
Bare 'gbrain check-backlinks' exits with a usage error; the CLI requires
'check' or 'fix' (src/commands/backlinks.ts runBacklinks). The hygiene step
now invokes 'check-backlinks check', matching every other invocation in the
skill pack.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(skills): regenerate skills.lock.json after master fix-wave
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
runServe()'s stdio path flips console-prefix's module-global
stdout→stderr redirect (#3844). bun test runs every file in one
process, so after sweep.test.ts's serve-wiring tests the flag stays
on and any later file pinning slog's stdout routing fails
(test/sync-all-parallel.test.ts, test/console-prefix.test.ts) —
shard-composition dependent, so it surfaces as a flake. Same reset
the donor harness (test/serve-stdio-lifecycle.test.ts) already
carries.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(core): execution-environment detection — local | cloud-sandbox | ephemeral-container
detectExecutionEnvironment() + isCredentialInjectingProxy() with injected
signals (CLAUDE_CODE_REMOTE, cse_ session-id prefix, proxy-injected token
placeholder, anthropic-egress proxy JWT, container markers). binaryOnPath
moves here as the canonical PATH probe. autopilot's detectInstallTarget
ephemeral branch now routes through the shared detector.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): repo-visibility ladder + per-turn Stop push + same-session failure banner
The durability lane. One repo-visibility verdict for every consumer (REST
first — never GraphQL, which sandbox proxies pin; authed ls-remote + an
attributed anonymous probe as the git-protocol fallback), replacing three
drifted probes. A 200 counts as public only with advertisement proof; a
401/404 counts as private-signal only with an auth challenge — fail-closed
in BOTH directions. Private verdicts cache 1h (private-only, per origin).
gbrain hook stop now spawns a debounced detached push per turn (per-root
state; cloud-sandbox defaults to every turn, elsewhere 5 min; a failing
status bypasses the debounce), closing the /exit and VM-reclaim gaps.
Push status is per workspace root, read through one shared reader by the
user-prompt banner (additionalContext + systemMessage — visible to the
human, not just the model), the SessionStart note, and doctor. Escape
hatches for self-hosted git: flag > env > file-plane config key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): environment-aware install — honest cron skip, cloud repo-create guard, execution_env surfaces
installDurabilityCron probes for crontab before writing anything (containers
and cloud sandboxes ship without one — expected, reported as an honest skip
naming the event-driven pushes that still cover persistence). The repo phase
installs the container-friendly harden half (post-commit hook, no scheduler)
outside local machines. createPrivateRepo fails fast in cloud sandboxes with
the flow that works (create outside, open the session ON the repo, attach).
bootstrap verify gains a never-gating execution_env check; bootstrap status
--json carries execution_environment for installing agents to branch on.
Also fixes a live-PATH resolution class: binaryOnPath and the crontab execs
now pass the current env explicitly (Bun resolves against the startup
snapshot otherwise).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): lifecycle hygiene — uninstall teardown, doctor job liveness, .mcp.json out of the repo, honest persistence copy
uninstall now tears down the durability wiring it installed (launchd/cron
job, untracked post-commit hook, credential wiring — the committed helper
and AGENTS rules stay). doctor gains bootstrap_durability_job: presence +
LIVENESS (launchctl load-state, crontab line, pull-log freshness) — a plist
on disk with a dead job no longer reads as healthy. Rendered .gitignore now
covers .mcp.json (absolute machine paths must not land in the private
repo); verify warns on pre-fix installs that committed it; the never-built
state/mcp.json promise is gone from GITHUB.md. Persistence copy tells the
truth everywhere: event-driven pushes do the durability work, the 30-min
job is a multi-machine pull freshener. Post-commit hook install/removal is
worktree-safe (git-path resolution; the git marker is a FILE there) and the
cron wrapper's self-disable tests the repo dir, not its git marker. Two
follow-up TODOs filed (plugin hook distribution; Channels push lane).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bootstrap): committed hook carrier for cloud + cloud-setup-script emitter + cloud runbook
Cloud sessions clone fresh and snapshot hook config at session start — the
gitignored settings.local.json never exists there, so hooks never fired in
cloud at all. Cloud installs now write the repo-COMMITTED .claude/settings.json
with PATH-resolved, fail-open commands (no machine paths; a host without the
binary no-ops); local installs keep settings.local.json; the writers enforce
that one event never fires from both carriers, and removal cleans both.
New: gbrain bootstrap cloud-setup-script prints the paste-ready environment
setup script (npm transport — bun fetching is proxy-incompatible in cloud;
never the unrelated npm-registry package). Runbook gains a NEVER FABRICATE
TOOLING hard rule, a cloud-sandbox section (expected degradations as facts to
relay, the attach-first flow), and failure-table rows for the proxy-403 and
missing-crontab signatures. Codex lane stated honestly: AGENTS.md Gate 2 now
has the pull-side push-health check. Guide documents the new knobs and the
cloud contract; llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(bootstrap): per-turn push e2e chain, cloud-sandbox sim, per-root status reader migration + registry regen
bootstrap-persistence e2e gains the Stop-hook chain (one stop banks the turn
to the real bare remote; the debounce holds across stops; debounce-0 lands
consecutive turns) and fixes a pre-existing post-#4024 break: repoPhaseComplete
required a github-parseable origin, so session-end pushes deferred FOREVER for
self-hosted/file-transport origins — non-github repo_urls now bind by exact
URL equality (redirect protection preserved). Degraded-modes e2e gains the
cloud-sandbox simulation (status reports the environment; repo creation
refuses with the attach flow). workspace-push tests read per-root status
through the shared reader. Flag registry regenerated (new git argv literals
from the git-path/ls-files calls — the accepted argv-bleed class).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(doctor): categorize bootstrap_durability_job (categories drift guard)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): adversarial-review hardening — fail-closed privacy, no push-storm, no exfil paths
Both Claude and Codex adversarial passes ran; 11 findings fixed (union):
- Non-github 401+WWW-Authenticate no longer reads as private (RFC 7235 makes
the header mandatory on every 401, so a middlebox 401s identically) — the
top exfil path both models flagged; now unverifiable/fail-closed, operator
confirms via the escape hatch. github.com still needs x-github-request-id.
- Escape hatches downgrade ONLY 'unverifiable' — a PROVEN-public origin still
refuses (hatches never authorize a public push).
- treeNeedsPush measures against origin/<branch> (the push's own ref), not
@{u}: a no-upstream branch no longer reports a committed-but-unpushed tree
as push_clean and silently strands it.
- [D20] failing-retry uses a fixed 60s floor, not min(debounce,60s) — cloud
debounce=0 no longer re-runs the network ladder every turn.
- Committed hook carrier: dedupe/suppress only on the EXACT portable-command
shape, not a 'gbrain hook' substring (blocks the suppress-local-run-evil
supply-chain vector); GBRAIN_HOME refused in the committed carrier.
- push-status reason sanitized (charset+length) at every surface (banner,
doctor, status blob) so remote git stderr can't inject via the remediation.
- Per-root state: ghost-root records (deleted workspaces) filtered so a dead
failing record can't re-fire the banner forever; uninstall removes them.
- statusReport support blob reads push status through the shared per-root
reader; visibility cache strips URL userinfo (no PAT persisted).
- anonProbe strips userinfo + redirect:manual + SSRF flags + --end-of-options
on ls-remote; cron self-disable uses git rev-parse (worktree-safe both
ways); cloud-setup-script fails loud on a broken update; durability liveness
won't certify a never-run crontab as live; config get/unset resolve the
dotted file-plane keys; .bak/.broken gitignored; typed config fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* v0.45.8.0 feat(bootstrap): first-class cloud-sandbox install + per-turn persistence + fail-closed privacy ladder
VERSION + package.json + CHANGELOG + TODOS + llms bundles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: sync KEY_FILES + README for the cloud-DX wave (v0.45.8.0)
New KEY_FILES entries for execution-env.ts and repo-visibility.ts; updated the
bootstrap repo/hooks, workspace-push, brain-repo-durability, and hook.ts
entries to current behavior (ladder verification, committed cloud hook carrier,
per-root push status, crontab probe + liveness, per-turn stop push + banner).
README's Claude Code line now states per-turn + cloud persistence honestly.
llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): doc-review pass — attach records repo_url (enables cloud persistence), verify reads per-root push status, honest docs
Cross-model doc review (Codex) caught that the headline cloud-persistence
claim was hollow and two docs over-claimed:
- attach now records repo_url from the adopted origin, so the no-daemon push
gate (repoPhaseComplete) recognizes the repo phase as done — WITHOUT this,
the per-turn/session-end pushes deferred forever after an attach, which is
the ONLY install path in a cloud sandbox (repo is refused there). Privacy is
still enforced at push time by the ladder.
- bootstrap verify's push_probe reads the shared per-root reader [D8], not the
legacy single file — a fresh v0.45.8 install no longer reports 'no push
recorded' when per-root status exists.
- Docs corrected to match code: runbook stamp → 0.45.8.0; README states the
per-turn cadence honestly (debounced local, next-turn failure notice);
GITHUB.md qualifies auto-push as Claude Code (Codex is pull); KEY_FILES says
repo/status use REST (not the full ladder) and drops the stale --push;
bootstrap.md hooks-location covers both carriers; README verbs surface says
seven. llms bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bootstrap): regenerate vendored template-repo tree + isolate env-mutating tests (verify gate)
The verify CI gate caught two things the piecemeal local runs missed:
- templates/bootstrap/template-repo/ is a GENERATED tree; the source-template
edits (AGENTS.md Gate 2, CLAUDE.md cloud note) plus the version stamp had
drifted. Regenerated from source (check:bootstrap-templates green).
- test/bootstrap-repo.test.ts + test/durability-cron.test.ts newly mutated
process.env in non-serial files; converted to withEnv() (check:test-isolation
green) — no .serial rename needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(ambient-recall): pin a keyless gateway so delta/context_pack writes never fire a real embed
Root cause of the shard-2 CI red after the 0.45.9.0 re-bump: adding this
wave's test files reshuffled the weight-packed shards, moving
ambient-recall.test.ts next to a neighbor that leaks CI's dummy
OPENAI_API_KEY (sk-test-*) into the gateway singleton (the bunfig preload
configures with env:{...process.env}, and a present-but-invalid key turns
remember's keyless-degrade embed into a hard 401). The delta/context_pack
tests exercise cursor + budget logic, not embedding quality, so this pins a
keyless gateway (env:{}) in beforeAll — isAvailable('embedding') is false,
writeSingleFact degrades (degraded_dedup) with no HTTP call, and the file is
deterministic regardless of shard bin-packing. Verified: passes with
OPENAI_API_KEY=sk-test-* set (the CI condition). Master's own comment in
legacy-embedding-preload.ts already warns 'adding any test file reshuffles
the mines'; this makes the file immune.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Both are CI-enforced on every VERSION bump (check-bootstrap-tag.sh,
check-bootstrap-templates.sh) but were missing from the table that
release flows read first.
Co-authored-by: test <test@test.co>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Every fix reviewed, tested in isolation, adversarially verified, security
reviewed, and re-tested as one composed branch. Full attribution preserved
via per-commit Co-Authored-By trailers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
file_list now routes through sqlQueryForEngine(ctx.engine); the pin keeps
the identical BigInt-normalization invariant but mocks the new seam
instead of the retired module-global db connection.
The wave's sync changes make the cycle sync phase lazily import
commands/sync.ts, whose static embedding.ts imports must all resolve
against the test's module mock (a missing name is a load-time
SyntaxError). Stubs mirror the real pure functions.