Closes 10 audit findings from 4-agent wave (critic + security +
architect + validator) on v0.21.0.
CRITIC HIGH (5):
H1 s3_cloud::commit() was listing with delimiter='/' — nested
writes silently dropped from manifest hash. Added
list_recursive() (no delimiter), filter manifest-*.json from
hash input.
H2 S3Cfg access_key_env + secret_key_env were advertised in TOML
but never read. Wired via resolve_explicit_creds() with
aws-credential-types. Partial-set or empty-resolve → error.
H3 display::sanitize_display missing in detach.rs + mount.rs
(regression of v0.19.2 L9 ANSI injection fix). Applied at 8
print sites. 2 new integration tests.
H4 adapters/jsonmcp.rs RESTORED (was lost in earlier merge).
107 LOC shared module: load_json_or_empty / upsert_under_key /
remove_under_key / persist. claude_code 163→105, cursor 165→106,
zed 178→114. Unified error handling via ConfigParseError.
H5 ENV_LOCK shared across kei-store tests. New test_env.rs (24 LOC)
exposed under cfg(any(test, feature='s3')). github.rs +
s3_cloud/tests.rs + s3_smoke.rs all use shared mutex. Fixes
parallel-test race on KEI_STORE_S3_ENDPOINT.
SECURITY HIGH (2):
SEC-H1 scripts/install-actionlint.sh — added sha256 verify
(shasum/sha256sum) before extract. ACTIONLINT_SHA256_OVERRIDE
env var for CI injection. Per-platform constants marked
[UNVERIFIED: SKIP] pending live checksums.txt fetch (agent had
no WebFetch this session — user follow-up: paste from
https://github.com/rhysd/actionlint/releases/download/v1.7.12/checksums.txt).
SEC-H2 S3 SSRF/IMDS guard. validate_endpoint() rejects:
loopback (127/8, ::1, localhost), link-local (169.254/16,
fe80::/10), metadata hostnames (google/azure). Override via
KEI_STORE_S3_ALLOW_INTERNAL=1. HTTP rejected unless
KEI_STORE_S3_ALLOW_INSECURE=1. Custom endpoint now REQUIRES
explicit creds (no IMDS chain leak via third-party endpoint).
4 reject + 3 accept tests pass.
POLISH (3):
D1 docs/USB-BRAIN-GUIDE.md — ⚠️ WARNING block under Prerequisites:
exFAT/FAT32 NOT safe for multi-client attach (SQLite WAL needs
shared-mem mmap). Use ONE client at a time on those FSes.
New Troubleshooting entry 'SQLite corruption on mount-attach'.
D2 '~5 MB release binary growth' now labelled [estimate, E5 —
not yet measured] in CHANGELOG.md + s3_cloud/mod.rs header.
D3 scripts/validate-workflow-shas.sh exits 2 (not 0) when
UNVERIFIED_COUNT > 0 and GITHUB_TOKEN absent. Distinguishes
'network denied' from 'all good'.
REAL VERIFICATION (pasted by agent):
cargo check -p keisei -p kei-store: Finished (clean)
cargo test -p keisei --release: 30 passed 0 failed
cargo test -p kei-store --release: 10 + 9 passed (default features)
cargo test -p kei-store --features s3 --release:
31 + 9 + 6 = 46 passed (with s3)
bash -n scripts/*.sh: OK
regen-counts.sh --check: no drift
Constructor Pattern: largest new src 200 LOC (s3_cloud/mod.rs, at
limit). jsonmcp.rs 107 LOC. test_env.rs 24 LOC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
323 lines
11 KiB
Markdown
323 lines
11 KiB
Markdown
# USB Exobrain — Step-by-Step Test Recipe
|
||
|
||
> Goal: put a KeiSeiKit "brain" on a physical USB drive or flash card, mount it into Claude Code (and Cursor / Continue / Zed if installed), verify MCP tools are live, unplug — verify clean detach.
|
||
>
|
||
> Target audience: someone trying the v0.21 exobrain feature for the first time on macOS (adapt paths for Linux).
|
||
|
||
---
|
||
|
||
## 0. Prerequisites
|
||
|
||
On the host (your laptop):
|
||
|
||
- KeiSeiKit installed: `./install.sh --profile=full` ran successfully, `cargo test -p keisei` shows 28/28 pass.
|
||
- `keisei` CLI on PATH: `ls ~/.claude/agents/_primitives/_rust/target/release/keisei` returns the binary. Optionally `ln -sf ~/.claude/agents/_primitives/_rust/target/release/keisei ~/.local/bin/keisei` so you can just type `keisei`.
|
||
- Claude Code installed (or Cursor / Continue / Zed — the CLI auto-detects all four).
|
||
- A USB stick or flash card mounted on macOS — expected path `/Volumes/<NAME>` (e.g. `/Volumes/EXOBRAIN`).
|
||
|
||
On the USB drive:
|
||
|
||
- Filesystem: exFAT or APFS (not HFS+ if you want cross-platform). FAT32 works but has 4 GB per-file limit — fine for brain dir (< 200 MB total even with 5 platform binaries).
|
||
- Free space: ~500 MB recommended (5 mcp-server binaries × ~90 MB each = ~450 MB, plus room for memory/artifacts SQLite).
|
||
|
||
> ⚠️ **WARNING — exFAT / FAT32 are NOT safe for multi-client attach.**
|
||
>
|
||
> SQLite WAL mode (used by `kei-memory`, `kei-artifact`, `kei-social-store` inside a brain) requires a filesystem with reliable shared-memory mmap. exFAT and FAT32 do NOT provide this, and `keisei mount` (multi-client fan-out in step 7 below) WILL corrupt the memory DBs if the brain lives on one of those filesystems.
|
||
>
|
||
> - On exFAT / FAT32, use the brain with **ONE client at a time** (single `keisei attach --scope=user`). Do not run `keisei mount`.
|
||
> - For reliable multi-client use, put the brain on **APFS (macOS)**, **ext4 (Linux)**, or **NTFS (Windows)**. exFAT is fine for single-client or read-only cross-platform transport.
|
||
> - If you've already mounted a brain from exFAT and suspect corruption, see "SQLite corruption on mount-attach" in Troubleshooting.
|
||
|
||
---
|
||
|
||
## 1. Create the brain directory
|
||
|
||
Replace `EXOBRAIN` with your actual USB volume name.
|
||
|
||
```bash
|
||
BRAIN=/Volumes/EXOBRAIN/my-brain
|
||
mkdir -p "$BRAIN"/{bin,memory,artifacts,manifests}
|
||
```
|
||
|
||
Structure after step 1:
|
||
```
|
||
/Volumes/EXOBRAIN/my-brain/
|
||
├── bin/
|
||
├── memory/
|
||
├── artifacts/
|
||
└── manifests/
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Download MCP server binaries for every platform
|
||
|
||
Grab the 5 single-binary compiles from the latest GitHub release. For v0.21.0:
|
||
|
||
```bash
|
||
BASE=https://github.com/KeiSei84/KeiSeiKit/releases/download/v0.21.0
|
||
cd "$BRAIN/bin"
|
||
|
||
# macOS Apple Silicon
|
||
curl -fL -O "$BASE/kei-mcp-server-darwin-arm64"
|
||
curl -fL -O "$BASE/kei-mcp-server-darwin-arm64.sha256"
|
||
|
||
# macOS Intel
|
||
curl -fL -O "$BASE/kei-mcp-server-darwin-x64"
|
||
curl -fL -O "$BASE/kei-mcp-server-darwin-x64.sha256"
|
||
|
||
# Linux x86_64
|
||
curl -fL -O "$BASE/kei-mcp-server-linux-x64"
|
||
curl -fL -O "$BASE/kei-mcp-server-linux-x64.sha256"
|
||
|
||
# Linux arm64 (may be unavailable on older releases — continue-on-error in CI)
|
||
curl -fL -O "$BASE/kei-mcp-server-linux-arm64" 2>/dev/null || echo "skipped linux-arm64"
|
||
|
||
# Windows x86_64
|
||
curl -fL -O "$BASE/kei-mcp-server-windows-x64.exe"
|
||
curl -fL -O "$BASE/kei-mcp-server-windows-x64.exe.sha256"
|
||
|
||
# Verify every downloaded binary against its .sha256
|
||
for f in kei-mcp-server-*.sha256; do
|
||
shasum -a 256 -c "$f" || echo "FAIL: $f"
|
||
done
|
||
|
||
# Strip macOS Gatekeeper quarantine + chmod +x on Unix binaries
|
||
chmod +x kei-mcp-server-darwin-* kei-mcp-server-linux-* 2>/dev/null || true
|
||
xattr -d com.apple.quarantine kei-mcp-server-darwin-* 2>/dev/null || true
|
||
```
|
||
|
||
Expected: every `shasum -c` prints `OK`.
|
||
|
||
---
|
||
|
||
## 3. Write `manifest.toml` (schema v2, per-platform)
|
||
|
||
```bash
|
||
cat > "$BRAIN/manifest.toml" <<'EOF'
|
||
[brain]
|
||
schema_version = 2
|
||
name = "my-brain"
|
||
created = "2026-04-22T00:00:00Z"
|
||
|
||
[paths]
|
||
memory = "memory/"
|
||
artifacts = "artifacts/"
|
||
manifests = "manifests/"
|
||
|
||
[paths.mcp_server]
|
||
darwin-arm64 = "bin/kei-mcp-server-darwin-arm64"
|
||
darwin-x64 = "bin/kei-mcp-server-darwin-x64"
|
||
linux-x64 = "bin/kei-mcp-server-linux-x64"
|
||
linux-arm64 = "bin/kei-mcp-server-linux-arm64"
|
||
windows-x64 = "bin/kei-mcp-server-windows-x64.exe"
|
||
EOF
|
||
```
|
||
|
||
Validation: `keisei` rejects the brain if `name` contains anything outside `^[a-z][a-z0-9_-]{0,63}$`, if any path is absolute, if any path contains `..`, or if the root is a symlink.
|
||
|
||
---
|
||
|
||
## 4. Verify the brain loads before attaching
|
||
|
||
```bash
|
||
keisei list-adapters
|
||
```
|
||
|
||
Expected: 2-column table showing every adapter, `detected: yes` for at least `claude-code` (the others depend on whether Cursor / Continue / Zed are installed on this host).
|
||
|
||
If you want a dry-run of the brain itself without touching any client config:
|
||
```bash
|
||
# Loads manifest, validates path confinement + schema — then errors because
|
||
# nothing attached yet. Use the error to confirm load-path is clean.
|
||
keisei status
|
||
# Expected: "no brain attached"
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Attach the brain to Claude Code
|
||
|
||
Single-client, user scope (the safe default):
|
||
|
||
```bash
|
||
keisei attach "$BRAIN" --scope=user
|
||
```
|
||
|
||
Expected output (literally):
|
||
```
|
||
attached brain 'my-brain' to claude-code (user scope)
|
||
brain path: /Volumes/EXOBRAIN/my-brain
|
||
mcp server: /Volumes/EXOBRAIN/my-brain/bin/kei-mcp-server-darwin-arm64
|
||
client cfg: /Users/you/.claude/settings.json
|
||
marker: /Users/you/.keisei/attached.toml
|
||
run /help in Claude Code to verify the MCP server is reachable
|
||
```
|
||
|
||
The last line is the client-specific post-attach hint. Each adapter emits its own.
|
||
|
||
---
|
||
|
||
## 6. Verify in Claude Code
|
||
|
||
Close and reopen Claude Code (or run `/help` → "MCP servers" section). You should see `my-brain` listed with the `/Volumes/EXOBRAIN/my-brain/bin/kei-mcp-server-darwin-arm64` command.
|
||
|
||
```bash
|
||
# On the host, inspect what just got written:
|
||
cat ~/.claude/settings.json | jq '.mcpServers["my-brain"]'
|
||
# Expected:
|
||
# {
|
||
# "command": "/Volumes/EXOBRAIN/my-brain/bin/kei-mcp-server-darwin-arm64",
|
||
# "env": {
|
||
# "KEISEI_BRAIN_ROOT": "/Volumes/EXOBRAIN/my-brain"
|
||
# }
|
||
# }
|
||
```
|
||
|
||
Run a Claude Code command that uses a tool from kei-mcp-server (any of the 25+ MCP tool endpoints). If Claude Code reports success, the brain is live.
|
||
|
||
---
|
||
|
||
## 7. Multi-client mount (all detected clients at once)
|
||
|
||
```bash
|
||
keisei mount "$BRAIN"
|
||
```
|
||
|
||
Fan-out attach to every client detected on this host. For each, writes to user-scope config (`~/.claude/settings.json`, `~/.cursor/mcp.json`, `~/.continue/config.json`, `~/Library/Application Support/Zed/settings.json`).
|
||
|
||
Expected summary:
|
||
```
|
||
mounted brain 'my-brain' to:
|
||
claude-code ✓
|
||
cursor ✓
|
||
continue ✗ (not detected)
|
||
zed ✓
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Project-scope attach (Claude Code / Cursor only)
|
||
|
||
Useful for per-project brains that stay with the repo:
|
||
|
||
```bash
|
||
cd ~/path/to/your-repo
|
||
keisei attach "$BRAIN" --scope=project
|
||
```
|
||
|
||
Writes to `./.claude/settings.json` (inside the repo) instead of `~/.claude/`. Continue and Zed reject this scope with a clear error — they don't have per-project MCP config.
|
||
|
||
---
|
||
|
||
## 9. Verify brain health
|
||
|
||
```bash
|
||
keisei status
|
||
```
|
||
|
||
Expected:
|
||
```
|
||
attached: my-brain
|
||
brain path: /Volumes/EXOBRAIN/my-brain
|
||
attached at: 2026-04-22T17:30:00Z
|
||
clients:
|
||
- claude-code (user scope) ~/.claude/settings.json
|
||
- cursor (user scope) ~/.cursor/mcp.json
|
||
- zed (user scope) ~/Library/Application Support/Zed/settings.json
|
||
health: OK (manifest readable, mcp_server binary exists)
|
||
```
|
||
|
||
If you unplug the USB, re-run `keisei status`:
|
||
```
|
||
health: WARN — mcp_server binary at <path> is not reachable
|
||
(brain was mounted at /Volumes/EXOBRAIN/my-brain, which no longer exists)
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Detach — clean up when done
|
||
|
||
```bash
|
||
keisei detach
|
||
```
|
||
|
||
Strips the `mcpServers.my-brain` entry from every client's config (preserving any other MCP servers you have configured), then deletes the marker.
|
||
|
||
Expected:
|
||
```
|
||
detached 'my-brain' from:
|
||
claude-code ✓
|
||
cursor ✓
|
||
zed ✓
|
||
marker removed: ~/.keisei/attached.toml
|
||
```
|
||
|
||
Confirm:
|
||
```bash
|
||
keisei status # → "no brain attached"
|
||
jq '.mcpServers' ~/.claude/settings.json # → your own entries only
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Eject the USB safely
|
||
|
||
```bash
|
||
# macOS:
|
||
diskutil eject /Volumes/EXOBRAIN
|
||
|
||
# Linux:
|
||
umount /media/$USER/EXOBRAIN
|
||
```
|
||
|
||
Physically unplug.
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### "BrainNotFound" on attach
|
||
- Check `/Volumes/EXOBRAIN/my-brain/manifest.toml` exists
|
||
- Check the path to `keisei attach` is absolute (`/Volumes/...`), not relative
|
||
|
||
### "PathEscape" on attach
|
||
- Every path under `[paths]` must be relative and resolve inside the brain root. No `../`, no absolute paths.
|
||
|
||
### "BrainIsSymlink" on attach
|
||
- `/Volumes/EXOBRAIN/my-brain` is a symlink to elsewhere. Pass the real resolved path instead — `keisei` refuses symlink roots to prevent accidental host-dir pivot via crafted USB.
|
||
|
||
### "NoPlatformBinary" on Claude Code first-use
|
||
- Your platform's binary isn't in `bin/`. Check `std::env::consts::{OS, ARCH}` on your host — the expected filename is `kei-mcp-server-<os-renamed>-<arch-renamed>` where macos→darwin, x86_64→x64, aarch64→arm64.
|
||
|
||
### Claude Code can't spawn the MCP server
|
||
- Ensure `chmod +x` applied to the binary
|
||
- On macOS: `xattr -d com.apple.quarantine <binary>` to clear Gatekeeper
|
||
- Check the binary actually runs standalone: `/Volumes/EXOBRAIN/my-brain/bin/kei-mcp-server-darwin-arm64 --help`
|
||
|
||
### "NameConflict" on attach
|
||
- Another MCP server with the same name already exists in the client's config. Either rename your brain (`name` in manifest.toml) or remove the existing entry manually.
|
||
|
||
### SQLite corruption on mount-attach
|
||
- `kei-memory` / `kei-artifact` / `kei-social-store` databases show "disk image is malformed" or "database is locked" errors after a `keisei mount` on a USB drive.
|
||
- Root cause: the brain lives on **exFAT or FAT32**, which do not reliably support SQLite WAL-mode shared-memory mmap. Multi-client attach corrupts the DB.
|
||
- Fix:
|
||
1. Copy the brain dir to an **APFS** (macOS) / **ext4** (Linux) volume, either internal disk or a reformatted USB.
|
||
2. Re-attach via `keisei attach <new-path>` or `keisei mount <new-path>`.
|
||
3. For single-client use on the same exFAT drive, restrict to `keisei attach --scope=user` only — do NOT use `keisei mount`.
|
||
- Prevention: re-read step 0 → Prerequisites → "⚠️ WARNING" above.
|
||
|
||
---
|
||
|
||
## What this tests end-to-end
|
||
|
||
1. **Portable brain** — works from read-only USB, no installer needed on the brain itself
|
||
2. **Per-platform dispatch** — schema v2 picks the right binary based on host OS+arch
|
||
3. **Multi-client fan-out** — `keisei mount` attaches to every detected client in one call
|
||
4. **Clean detach** — zero residue in host configs, preserves unrelated MCP servers
|
||
5. **Safety gates** — path confinement, name regex, symlink rejection, size bound (64 KiB manifest cap)
|
||
6. **Schema v1 compat** — drop in an older v1 brain with a single-string mcp_server, still works
|
||
|
||
If all 11 steps above pass, the v0.21 exobrain is production-ready for single-user workflows.
|
||
|
||
For shared-brain scenarios (team members all mounting the same brain over git / S3) see the `kei-store` backend docs — S3 backend via `keisei attach s3://my-bucket/brain/` (requires `--features s3` at install).
|