Merge feat/v0.22-fs-warn-battle-matrix — FS warn + battle-test matrix + USB docs split
This commit is contained in:
commit
5c3f2a57cc
14 changed files with 649 additions and 288 deletions
|
|
@ -21,6 +21,9 @@ _primitives/_rust/target/release/kei-changelog \
|
|||
> ships must be replaced with the real commit summary before release.
|
||||
|
||||
### Added
|
||||
- **primitives/keisei (v0.22 Track C — filesystem-type advisory):** new `fs_type.rs` cube (`<110 LOC`) classifies the brain root via `statfs(2)` on macOS + Linux and returns `FsWarning::{None,ExFat,Fat32,Unknown}`. Windows support deferred (returns `Unknown` until `GetVolumeInformationW` lands). `Brain::load` now prints a stderr advisory when exFAT / FAT32 is detected — SQLite WAL shared-mmap is unreliable there and `keisei mount` (multi-client) WILL corrupt `kei-memory` / `kei-artifact` / `kei-social-store` DBs. Warning is non-blocking — single-client `keisei attach` on exFAT stays supported. New runtime dep `libc = "0.2"` (unix-only). Two new integration tests (`brain_load_on_typical_filesystem_no_warn`, `fs_type_detection_returns_none_on_standard_fs`) — suite now 32/32 pass.
|
||||
- **tests/battle (v0.22 Track C — distro matrix):** two new Dockerfiles alongside the existing `ubuntu:24.04` image. `Dockerfile.install-test-alpine` (Alpine 3.19 — musl libc, exposes musl-static-link quirks in `rusqlite` / `git2` / `aws-sdk-s3`). `Dockerfile.install-test-debian` (Debian 12 bookworm — glibc, different apt structure from Ubuntu). `README.md` documents the 3-image matrix and documents known musl-static-link failures as matrix signal rather than regression.
|
||||
- **docs (v0.22 Track C — USB guide platform split):** `USB-BRAIN-GUIDE.md` restructured into a TOC + platform-agnostic preamble (prerequisites, exFAT/FAT32 warning, invariants, troubleshooting). Three new platform-specific walkthroughs: `USB-BRAIN-GUIDE-macos.md` (Gatekeeper `xattr`, `/Volumes/`, `diskutil`), `USB-BRAIN-GUIDE-linux.md` (`/media/$USER/`, `umount`, ext4, optional systemd-udev auto-attach), `USB-BRAIN-GUIDE-windows.md` (PowerShell, drive letter, NTFS, `Dismount-Volume`, FS-advisory returns `Unknown` caveat).
|
||||
- **primitives (v0.21 — keisei SSoT relocation + `Scope` enum):**
|
||||
- Marker file relocated from `~/.claude/keisei-attached.toml` to `~/.keisei/attached.toml`. `~/.claude/` is Claude-Code-specific territory and should not host cross-adapter keisei state. `config::read()` performs a one-shot migration the first time it runs under v0.21: if the legacy file exists and the new location is empty, the marker moves over (new file written, legacy file deleted) and a stderr notice is emitted.
|
||||
- `Scope` enum (`user` / `project`) on the `ClientAdapter` trait. Adapters declare `supported_scopes()`; `config_path(scope)`, `attach(brain, scope)`, `detach(brain_name, scope)` are scope-aware. Claude Code and Cursor support both scopes; Continue and Zed are user-only. `keisei attach` gains `--scope=<user|project>` (default `user`); `keisei mount` stays host-wide (`Scope::User` fan-out by design).
|
||||
|
|
|
|||
1
_primitives/_rust/Cargo.lock
generated
1
_primitives/_rust/Cargo.lock
generated
|
|
@ -1952,6 +1952,7 @@ name = "keisei"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,8 @@ thiserror = "2"
|
|||
regex = { workspace = true }
|
||||
tempfile = "3"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ impl Brain {
|
|||
pub fn load(input: &Path) -> Result<Self> {
|
||||
v::reject_symlink_root(input)?;
|
||||
let root = v::canonicalize_root(input)?;
|
||||
crate::fs_type::warn_on_unsafe_fs(&root);
|
||||
let manifest = v::read_manifest(&root)?;
|
||||
v::validate_schema(&manifest)?;
|
||||
v::validate_name(&manifest.brain.name)?;
|
||||
|
|
|
|||
103
_primitives/_rust/keisei/src/fs_type.rs
Normal file
103
_primitives/_rust/keisei/src/fs_type.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
//! Filesystem type detection for brain root.
|
||||
//!
|
||||
//! Warns when the brain sits on exFAT / FAT32, where SQLite WAL shared-
|
||||
//! memory mmap (used by `kei-memory`, `kei-artifact`, `kei-social-store`)
|
||||
//! is unreliable and `keisei mount` (multi-client) will corrupt DBs.
|
||||
//! Single-client `keisei attach` stays supported, hence the warning is
|
||||
//! advisory, never blocking. Platform calls: `statfs(2)` on macOS +
|
||||
//! Linux; Windows returns `Unknown` until `GetVolumeInformationW` lands.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum FsWarning {
|
||||
None,
|
||||
ExFat,
|
||||
Fat32,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Print a stderr advisory when the brain root lives on exFAT / FAT32.
|
||||
/// Advisory only — load succeeds regardless. See [`detect_fs_warning`].
|
||||
pub fn warn_on_unsafe_fs(root: &Path) {
|
||||
match detect_fs_warning(root) {
|
||||
FsWarning::ExFat | FsWarning::Fat32 => {
|
||||
eprintln!(
|
||||
"[keisei] WARNING: brain root '{}' is on an exFAT/FAT32 filesystem. \
|
||||
SQLite WAL mode (used by kei-memory/artifact/social-store) is UNSAFE \
|
||||
on these filesystems. Use 'keisei attach' with ONE client at a time; \
|
||||
'keisei mount' (multi-client fan-out) WILL corrupt memory DBs. \
|
||||
See docs/USB-BRAIN-GUIDE.md for recommended filesystems.",
|
||||
root.display()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Classify the filesystem at `path`. NEVER returns `Result` — errors
|
||||
/// collapse to `Unknown` so this stays call-safe inside `Brain::load`.
|
||||
pub fn detect_fs_warning(path: &Path) -> FsWarning {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
return macos_detect(path);
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
return linux_detect(path);
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
let _ = path;
|
||||
FsWarning::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn macos_detect(path: &Path) -> FsWarning {
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let c_path = match CString::new(path.as_os_str().as_bytes()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return FsWarning::Unknown,
|
||||
};
|
||||
// SAFETY: zero-init POD + valid CString ptr; statfs writes into buf.
|
||||
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statfs(c_path.as_ptr(), &mut buf) } != 0 {
|
||||
return FsWarning::Unknown;
|
||||
}
|
||||
let name: String = buf
|
||||
.f_fstypename
|
||||
.iter()
|
||||
.take_while(|b| **b != 0)
|
||||
.map(|b| *b as u8 as char)
|
||||
.collect();
|
||||
match name.as_str() {
|
||||
"exfat" => FsWarning::ExFat,
|
||||
"msdos" => FsWarning::Fat32,
|
||||
_ => FsWarning::None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn linux_detect(path: &Path) -> FsWarning {
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
// man statfs(2) — magic numbers.
|
||||
const MSDOS_SUPER_MAGIC: i64 = 0x4d44;
|
||||
const EXFAT_SUPER_MAGIC: i64 = 0x2011_bab0;
|
||||
let c_path = match CString::new(path.as_os_str().as_bytes()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return FsWarning::Unknown,
|
||||
};
|
||||
// SAFETY: zero-init POD + valid CString ptr.
|
||||
let mut buf: libc::statfs = unsafe { std::mem::zeroed() };
|
||||
if unsafe { libc::statfs(c_path.as_ptr(), &mut buf) } != 0 {
|
||||
return FsWarning::Unknown;
|
||||
}
|
||||
match buf.f_type as i64 {
|
||||
EXFAT_SUPER_MAGIC => FsWarning::ExFat,
|
||||
MSDOS_SUPER_MAGIC => FsWarning::Fat32,
|
||||
_ => FsWarning::None,
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ mod config;
|
|||
mod detach;
|
||||
mod display;
|
||||
mod error;
|
||||
mod fs_type;
|
||||
mod fsx;
|
||||
mod list;
|
||||
mod mount;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ mod brain_validate;
|
|||
mod config;
|
||||
#[path = "../src/display.rs"]
|
||||
mod display;
|
||||
#[path = "../src/fs_type.rs"]
|
||||
mod fs_type;
|
||||
#[path = "../src/fsx.rs"]
|
||||
mod fsx;
|
||||
#[path = "../src/adapters/mod.rs"]
|
||||
|
|
@ -993,3 +995,41 @@ fn mount_sanitizes_control_chars_in_error_reason() {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// v0.22 Track C — filesystem type detection (fs_type.rs).
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn brain_load_on_typical_filesystem_no_warn() {
|
||||
// On the dev / CI host the tmpdir sits on APFS (macOS) or ext4
|
||||
// (Linux) — neither of which should trigger the exFAT/FAT32 warn.
|
||||
// We can't capture stderr from Brain::load easily, so we assert on
|
||||
// the primitive that drives the advisory instead. `None` means "no
|
||||
// warning would be emitted"; `Unknown` is the accepted fallback
|
||||
// on platforms where statfs isn't wired.
|
||||
let _g = setup_home();
|
||||
let brain_dir = tempfile::tempdir().unwrap();
|
||||
write_brain(brain_dir.path(), 1);
|
||||
|
||||
let _b = brain::Brain::load(brain_dir.path()).expect("load succeeds on normal fs");
|
||||
|
||||
let w = fs_type::detect_fs_warning(brain_dir.path());
|
||||
assert!(
|
||||
matches!(w, fs_type::FsWarning::None | fs_type::FsWarning::Unknown),
|
||||
"standard tmpdir should not be classed as exFAT/FAT32, got {w:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fs_type_detection_returns_none_on_standard_fs() {
|
||||
// Direct test of the primitive — no brain, no env mutation.
|
||||
let td = tempfile::tempdir().unwrap();
|
||||
let w = fs_type::detect_fs_warning(td.path());
|
||||
// Must NEVER flag exFAT / FAT32 on a host tmpdir — the latter
|
||||
// sits on APFS / ext4 / whatever the developer has locally.
|
||||
assert!(
|
||||
!matches!(w, fs_type::FsWarning::ExFat | fs_type::FsWarning::Fat32),
|
||||
"detect_fs_warning misclassified tmpdir as {w:?}"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
98
docs/USB-BRAIN-GUIDE-linux.md
Normal file
98
docs/USB-BRAIN-GUIDE-linux.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# USB Exobrain — Linux Walkthrough
|
||||
|
||||
> Platform-specific companion to `USB-BRAIN-GUIDE.md`. Read the top-level guide first for prerequisites, warnings, and invariants.
|
||||
|
||||
On Linux, auto-mounted removable media typically lands at `/media/$USER/<LABEL>` (GNOME, KDE, auto-mounters) or `/run/media/$USER/<LABEL>` (systemd-udisks2). Substitute your actual mount point below.
|
||||
|
||||
## 1. Create the brain directory
|
||||
|
||||
```bash
|
||||
BRAIN=/media/$USER/EXOBRAIN/my-brain
|
||||
mkdir -p "$BRAIN"/{bin,memory,artifacts,manifests}
|
||||
```
|
||||
|
||||
## 2. Download MCP server binaries
|
||||
|
||||
```bash
|
||||
BASE=https://github.com/KeiSei84/KeiSeiKit/releases/download/v0.21.0
|
||||
cd "$BRAIN/bin"
|
||||
for n in darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64.exe; do
|
||||
curl -fL -O "$BASE/kei-mcp-server-$n" 2>/dev/null || echo "skipped $n"
|
||||
curl -fL -O "$BASE/kei-mcp-server-$n.sha256" 2>/dev/null || true
|
||||
done
|
||||
|
||||
for f in kei-mcp-server-*.sha256; do sha256sum -c "$f"; done
|
||||
chmod +x kei-mcp-server-linux-* kei-mcp-server-darwin-* 2>/dev/null || true
|
||||
```
|
||||
|
||||
No Gatekeeper / `xattr` step on Linux. The `chmod +x` is still required — the executable bit is not restored by `curl`.
|
||||
|
||||
## 3. Write `manifest.toml` (schema v2)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 4. Verify + attach
|
||||
|
||||
```bash
|
||||
keisei list-adapters
|
||||
keisei status # "no brain attached"
|
||||
keisei attach "$BRAIN" --scope=user
|
||||
```
|
||||
|
||||
Marker lands at `~/.keisei/attached.toml`; Claude Code settings at `~/.claude/settings.json`.
|
||||
|
||||
## 5. Verify in Claude Code
|
||||
|
||||
```bash
|
||||
jq '.mcpServers["my-brain"]' ~/.claude/settings.json
|
||||
```
|
||||
|
||||
## 6. Multi-client mount
|
||||
|
||||
```bash
|
||||
keisei mount "$BRAIN"
|
||||
```
|
||||
|
||||
Linux adapter paths: `~/.claude/settings.json`, `~/.cursor/mcp.json`, `~/.continue/config.json`, `~/.config/zed/settings.json`.
|
||||
|
||||
## 7. Project-scope
|
||||
|
||||
```bash
|
||||
cd ~/path/to/your-repo
|
||||
keisei attach "$BRAIN" --scope=project # claude-code + cursor only
|
||||
```
|
||||
|
||||
## 8. Detach + unmount
|
||||
|
||||
```bash
|
||||
keisei detach
|
||||
umount /media/$USER/EXOBRAIN # or: sync && udisksctl unmount -b /dev/sdX1
|
||||
```
|
||||
|
||||
If `umount` returns "target is busy", close any shell with its CWD under the mount, then retry. `lsof +f -- /media/$USER/EXOBRAIN` lists open handles.
|
||||
|
||||
## Linux-specific troubleshooting
|
||||
|
||||
- **Filesystem detection** — `keisei` calls `statfs(2)` at load time. exFAT (`EXFAT_SUPER_MAGIC = 0x2011bab0`) and FAT32 (`MSDOS_SUPER_MAGIC = 0x4d44`) trigger the SQLite-WAL-unsafe advisory. Format the USB as ext4 for reliable multi-client use.
|
||||
- **Auto-mounted `noexec` partitions** — some distros mount removable media `noexec` by default. If the mcp server refuses to run, remount read-write-executable: `sudo mount -o remount,exec /media/$USER/EXOBRAIN`. Alternatively add a line to `/etc/fstab` keyed by UUID (`blkid /dev/sdX1`).
|
||||
- **Permissions drift** — if you copy a brain from macOS via tar / rsync, the executable bit may not survive. Re-apply `chmod +x bin/kei-mcp-server-*`.
|
||||
- **Optional — systemd auto-attach** — a `systemd-udev` rule can run `keisei attach <mount>/my-brain --scope=user` whenever a labelled stick shows up, and a matching `udev` remove rule can call `keisei detach`. Out of scope for this guide; see `ArchWiki: Udisks#Auto-mount`.
|
||||
97
docs/USB-BRAIN-GUIDE-macos.md
Normal file
97
docs/USB-BRAIN-GUIDE-macos.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# USB Exobrain — macOS Walkthrough
|
||||
|
||||
> Platform-specific companion to `USB-BRAIN-GUIDE.md`. Read the top-level guide first for prerequisites, warnings, and invariants.
|
||||
|
||||
## 1. Create the brain directory
|
||||
|
||||
```bash
|
||||
BRAIN=/Volumes/EXOBRAIN/my-brain
|
||||
mkdir -p "$BRAIN"/{bin,memory,artifacts,manifests}
|
||||
```
|
||||
|
||||
## 2. Download MCP server binaries
|
||||
|
||||
```bash
|
||||
BASE=https://github.com/KeiSei84/KeiSeiKit/releases/download/v0.21.0
|
||||
cd "$BRAIN/bin"
|
||||
for n in darwin-arm64 darwin-x64 linux-x64 windows-x64.exe; do
|
||||
curl -fL -O "$BASE/kei-mcp-server-$n"
|
||||
curl -fL -O "$BASE/kei-mcp-server-$n.sha256"
|
||||
done
|
||||
curl -fL -O "$BASE/kei-mcp-server-linux-arm64" 2>/dev/null || echo "skipped linux-arm64"
|
||||
|
||||
for f in kei-mcp-server-*.sha256; do shasum -a 256 -c "$f"; done
|
||||
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
|
||||
```
|
||||
|
||||
macOS Gatekeeper quarantines every downloaded binary; `xattr -d` clears the attribute. Re-run that line later if Claude Code refuses to spawn the mcp server.
|
||||
|
||||
## 3. Write `manifest.toml` (schema v2)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 4. Verify + attach
|
||||
|
||||
```bash
|
||||
keisei list-adapters
|
||||
keisei status # "no brain attached"
|
||||
keisei attach "$BRAIN" --scope=user
|
||||
```
|
||||
|
||||
Marker lands at `~/.keisei/attached.toml`; client settings at `~/.claude/settings.json`.
|
||||
|
||||
## 5. Verify in Claude Code
|
||||
|
||||
Close + reopen Claude Code, or run `/help` → MCP servers. Confirm from the shell:
|
||||
|
||||
```bash
|
||||
cat ~/.claude/settings.json | jq '.mcpServers["my-brain"]'
|
||||
```
|
||||
|
||||
## 6. Multi-client mount
|
||||
|
||||
```bash
|
||||
keisei mount "$BRAIN"
|
||||
```
|
||||
|
||||
Writes to `~/.claude/settings.json`, `~/.cursor/mcp.json`, `~/.continue/config.json`, `~/Library/Application Support/Zed/settings.json`.
|
||||
|
||||
## 7. Project-scope
|
||||
|
||||
```bash
|
||||
cd ~/path/to/your-repo
|
||||
keisei attach "$BRAIN" --scope=project # claude-code + cursor only
|
||||
```
|
||||
|
||||
## 8. Detach + eject
|
||||
|
||||
```bash
|
||||
keisei detach
|
||||
diskutil eject /Volumes/EXOBRAIN
|
||||
```
|
||||
|
||||
## macOS-specific troubleshooting
|
||||
|
||||
- **Claude Code can't spawn the MCP server** — check `chmod +x`, re-run `xattr -d com.apple.quarantine <binary>`, and confirm direct-run: `.../kei-mcp-server-darwin-arm64 --help`.
|
||||
- **"BrainIsSymlink" on attach** — `$BRAIN` is a symlink. Pass the resolved path; `keisei` refuses symlink roots.
|
||||
- **USB not at `/Volumes/EXOBRAIN`** — macOS auto-renames on name collision (`EXOBRAIN 1`). Confirm with `diskutil list`.
|
||||
115
docs/USB-BRAIN-GUIDE-windows.md
Normal file
115
docs/USB-BRAIN-GUIDE-windows.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# USB Exobrain — Windows Walkthrough
|
||||
|
||||
> Platform-specific companion to `USB-BRAIN-GUIDE.md`. Read the top-level guide first for prerequisites, warnings, and invariants.
|
||||
|
||||
Windows support is best-effort in v0.22 — `keisei` itself builds cleanly on Windows, but the filesystem-type advisory (`fs_type.rs`) returns `Unknown` pending a `GetVolumeInformationW` implementation. Meaning: the exFAT/FAT32 warning does NOT fire on Windows yet. Format your USB as NTFS manually for multi-client safety.
|
||||
|
||||
Shell snippets use PowerShell 7+.
|
||||
|
||||
## 1. Create the brain directory
|
||||
|
||||
Plug in the USB; Explorer will show a drive letter (e.g. `E:`).
|
||||
|
||||
```powershell
|
||||
$BRAIN = "E:\my-brain"
|
||||
New-Item -ItemType Directory -Path $BRAIN,"$BRAIN\bin","$BRAIN\memory","$BRAIN\artifacts","$BRAIN\manifests" -Force
|
||||
```
|
||||
|
||||
## 2. Download MCP server binaries
|
||||
|
||||
```powershell
|
||||
$BASE = "https://github.com/KeiSei84/KeiSeiKit/releases/download/v0.21.0"
|
||||
Push-Location "$BRAIN\bin"
|
||||
|
||||
$names = @(
|
||||
"darwin-arm64", "darwin-x64",
|
||||
"linux-x64", "linux-arm64",
|
||||
"windows-x64.exe"
|
||||
)
|
||||
foreach ($n in $names) {
|
||||
Invoke-WebRequest -Uri "$BASE/kei-mcp-server-$n" -OutFile "kei-mcp-server-$n" -ErrorAction SilentlyContinue
|
||||
Invoke-WebRequest -Uri "$BASE/kei-mcp-server-$n.sha256" -OutFile "kei-mcp-server-$n.sha256" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Get-ChildItem kei-mcp-server-*.sha256 | ForEach-Object {
|
||||
$expected = (Get-Content $_).Split(' ')[0]
|
||||
$target = $_.Name -replace '\.sha256$',''
|
||||
$actual = (Get-FileHash $target -Algorithm SHA256).Hash.ToLower()
|
||||
if ($actual -ne $expected) { Write-Error "FAIL: $target" }
|
||||
}
|
||||
Pop-Location
|
||||
```
|
||||
|
||||
No `chmod +x` on Windows — `.exe` is executable by extension. No `xattr` concept (Windows does not use HFS-style quarantine).
|
||||
|
||||
## 3. Write `manifest.toml` (schema v2)
|
||||
|
||||
```powershell
|
||||
@"
|
||||
[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"
|
||||
"@ | Set-Content -Path "$BRAIN\manifest.toml" -Encoding utf8NoBOM
|
||||
```
|
||||
|
||||
Note the `utf8NoBOM` encoding — the toml parser does not handle a UTF-8 BOM gracefully.
|
||||
|
||||
## 4. Verify + attach
|
||||
|
||||
```powershell
|
||||
keisei list-adapters
|
||||
keisei status # "no brain attached"
|
||||
keisei attach "$BRAIN" --scope=user
|
||||
```
|
||||
|
||||
Marker lands at `%USERPROFILE%\.keisei\attached.toml`. Claude Code settings at `%USERPROFILE%\.claude\settings.json`.
|
||||
|
||||
## 5. Verify in Claude Code
|
||||
|
||||
```powershell
|
||||
Get-Content "$HOME\.claude\settings.json" | ConvertFrom-Json | Select-Object -ExpandProperty mcpServers
|
||||
```
|
||||
|
||||
## 6. Multi-client mount
|
||||
|
||||
```powershell
|
||||
keisei mount $BRAIN
|
||||
```
|
||||
|
||||
## 7. Project-scope
|
||||
|
||||
```powershell
|
||||
cd C:\path\to\your-repo
|
||||
keisei attach $BRAIN --scope=project # claude-code + cursor only
|
||||
```
|
||||
|
||||
## 8. Detach + eject
|
||||
|
||||
```powershell
|
||||
keisei detach
|
||||
# Eject via PowerShell:
|
||||
$vol = Get-Volume -DriveLetter E
|
||||
$vol | Dismount-Volume -Force
|
||||
```
|
||||
|
||||
Or use the system tray "Safely Remove Hardware" icon — either path flushes pending writes before the device is physically removed.
|
||||
|
||||
## Windows-specific troubleshooting
|
||||
|
||||
- **FS advisory not firing** — v0.22 Windows build returns `Unknown` from `detect_fs_warning`. Format the stick as NTFS manually; exFAT is unsafe for `keisei mount`. A future release will wire `GetVolumeInformationW`.
|
||||
- **Long-path failures** — the brain root plus any nested manifest path must fit inside Windows' MAX_PATH (260 chars) unless you've opted into long paths via `HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1`. Keep `$BRAIN` short.
|
||||
- **Drive letter reassignment** — Windows may hand out a different letter next plug-in. The marker stores an absolute path, so a letter change breaks resolution. Re-attach after the new letter appears.
|
||||
- **Execution policy** — PowerShell will refuse to run helper scripts under default `Restricted` policy. `Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned` as a one-time fix.
|
||||
|
|
@ -1,323 +1,98 @@
|
|||
# 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.
|
||||
> Put a KeiSeiKit "brain" on a portable USB drive, mount it into Claude Code (and Cursor / Continue / Zed if installed), verify MCP tools, unplug — verify clean detach.
|
||||
>
|
||||
> Target audience: someone trying the v0.21 exobrain feature for the first time on macOS (adapt paths for Linux).
|
||||
> This top-level document holds prerequisites, the shared filesystem warning, the TOC, and platform-agnostic troubleshooting. Step-by-step walkthroughs live in the three platform sub-documents.
|
||||
|
||||
## Platform walkthroughs
|
||||
|
||||
- [macOS walkthrough](./USB-BRAIN-GUIDE-macos.md) — `/Volumes/<LABEL>`, APFS recommended, Gatekeeper / `xattr` handling.
|
||||
- [Linux walkthrough](./USB-BRAIN-GUIDE-linux.md) — `/media/$USER/<LABEL>`, ext4 recommended, `umount` / optional systemd-udev auto-attach.
|
||||
- [Windows walkthrough](./USB-BRAIN-GUIDE-windows.md) — drive letter, NTFS recommended, `Dismount-Volume` / PowerShell.
|
||||
|
||||
---
|
||||
|
||||
## 0. Prerequisites
|
||||
## 0. Prerequisites (all platforms)
|
||||
|
||||
On the host (your laptop):
|
||||
On the host:
|
||||
|
||||
- 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`).
|
||||
- KeiSeiKit installed: `./install.sh --profile=full` ran successfully, `cargo test -p keisei` shows 32/32 pass (v0.22).
|
||||
- `keisei` CLI on PATH: `ls ~/.claude/agents/_primitives/_rust/target/release/keisei` returns the binary. Symlink into `~/.local/bin/` or `%USERPROFILE%\bin` as convenient.
|
||||
- At least one supported client installed (Claude Code / Cursor / Continue / Zed) — `keisei` auto-detects all four.
|
||||
- A USB stick or flash card physically attached and mounted by the OS.
|
||||
|
||||
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.
|
||||
- Free space: ~500 MB recommended (5 mcp-server binaries × ~90 MB each + room for memory/artifacts SQLite).
|
||||
- Filesystem — see the warning below.
|
||||
|
||||
---
|
||||
|
||||
## 1. Create the brain directory
|
||||
## ⚠️ Filesystem warning — exFAT / FAT32 are NOT safe for multi-client attach
|
||||
|
||||
Replace `EXOBRAIN` with your actual USB volume name.
|
||||
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) WILL corrupt the memory DBs if the brain lives on one of those filesystems.
|
||||
|
||||
```bash
|
||||
BRAIN=/Volumes/EXOBRAIN/my-brain
|
||||
mkdir -p "$BRAIN"/{bin,memory,artifacts,manifests}
|
||||
```
|
||||
Since v0.22 `keisei` itself prints an advisory on stderr when it detects an exFAT / FAT32 root at load time (via `statfs(2)` on macOS + Linux; Windows returns `Unknown` pending `GetVolumeInformationW`). The warning is non-blocking — single-client `keisei attach` on exFAT is still a supported path.
|
||||
|
||||
Structure after step 1:
|
||||
```
|
||||
/Volumes/EXOBRAIN/my-brain/
|
||||
├── bin/
|
||||
├── memory/
|
||||
├── artifacts/
|
||||
└── manifests/
|
||||
```
|
||||
- On exFAT / FAT32: use the brain with **ONE client at a time** (`keisei attach --scope=user`). Do NOT run `keisei mount`.
|
||||
- For reliable multi-client use, format the stick as **APFS (macOS)**, **ext4 (Linux)**, or **NTFS (Windows)**. exFAT is fine for single-client or read-only cross-platform transport.
|
||||
- If you already ran `keisei mount` on an exFAT brain and see SQLite errors, see "SQLite corruption on mount-attach" in Troubleshooting below.
|
||||
|
||||
---
|
||||
|
||||
## 2. Download MCP server binaries for every platform
|
||||
## Brain safety invariants (all platforms)
|
||||
|
||||
Grab the 5 single-binary compiles from the latest GitHub release. For v0.21.0:
|
||||
The following load-time checks fire regardless of OS. They prevent the most common misuse and attack patterns:
|
||||
|
||||
```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`.
|
||||
- **Name regex** — `brain.name` must match `^[a-z][a-z0-9_-]{0,63}$` (lowercase letter start, 1-64 chars, word chars + hyphen).
|
||||
- **Relative paths** — every `[paths.*]` entry must be relative; no absolute paths, no `..` components. Canonical form must live under the brain root.
|
||||
- **Symlink reject** — the brain root itself cannot be a symlink. Pass the resolved path; this closes a USB→host pivot surface.
|
||||
- **Manifest size cap** — `manifest.toml` is bounded at 64 KiB.
|
||||
- **Schema** — `schema_version ∈ {1, 2}`. v1 uses single-string `mcp_server`; v2 uses per-platform map.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
## Platform-agnostic troubleshooting
|
||||
|
||||
### "BrainNotFound" on attach
|
||||
- Check `/Volumes/EXOBRAIN/my-brain/manifest.toml` exists
|
||||
- Check the path to `keisei attach` is absolute (`/Volumes/...`), not relative
|
||||
- Check `<BRAIN>/manifest.toml` exists.
|
||||
- Pass an absolute path to `keisei attach`, not a relative one.
|
||||
|
||||
### "PathEscape" on attach
|
||||
- Every path under `[paths]` must be relative and resolve inside the brain root. No `../`, no absolute paths.
|
||||
- Every `[paths.*]` entry 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.
|
||||
- The brain root is a symlink. Pass the resolved path instead — `keisei` refuses symlink roots to prevent accidental host-dir pivot via a 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`
|
||||
### "NoPlatformBinary" on 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`.
|
||||
|
||||
### "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.
|
||||
- An 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.
|
||||
- `kei-memory` / `kei-artifact` / `kei-social-store` databases show "disk image is malformed" or "database is locked" after `keisei mount` on a USB drive.
|
||||
- Root cause: the brain lives on **exFAT or FAT32**, which don't reliably support SQLite WAL 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.
|
||||
1. Copy the brain dir to an APFS (macOS) / ext4 (Linux) / NTFS (Windows) volume.
|
||||
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.
|
||||
3. For single-client use on the same exFAT drive, restrict to `keisei attach --scope=user`. Do NOT use `keisei mount`.
|
||||
|
||||
### Filesystem-warning advisory not appearing
|
||||
- `keisei` prints the exFAT/FAT32 warning to **stderr** (not stdout). Shells that redirect stderr away will hide it.
|
||||
- Windows `detect_fs_warning` returns `Unknown` in v0.22 — format as NTFS manually.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
1. **Portable brain** — works from a USB drive, no installer needed on the brain itself.
|
||||
2. **Per-platform dispatch** — schema v2 picks the right binary for the 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, manifest size bound, filesystem-type advisory (v0.22).
|
||||
6. **Schema v1 compat** — 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.
|
||||
If the walkthrough for your platform passes end-to-end, the v0.22 exobrain is production-ready for single-user workflows on that platform.
|
||||
|
||||
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).
|
||||
For shared-brain scenarios (team members all mounting the same brain over git / S3) see the `kei-store` backend docs — `keisei attach s3://my-bucket/brain/` requires `--features s3` at install.
|
||||
|
|
|
|||
42
tests/battle/Dockerfile.install-test-alpine
Normal file
42
tests/battle/Dockerfile.install-test-alpine
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# tests/battle/Dockerfile.install-test-alpine
|
||||
# Alpine 3.19 — musl libc. Common container base; exposes musl-vs-glibc
|
||||
# compatibility for rusqlite, git2, aws-sdk-s3 (all include C code).
|
||||
#
|
||||
# Build from repo root:
|
||||
# docker build -t keisei-battle-alpine:latest -f tests/battle/Dockerfile.install-test-alpine .
|
||||
# Run (default: minimal profile):
|
||||
# docker run --rm keisei-battle-alpine:latest
|
||||
# Override:
|
||||
# docker run --rm -e PROFILE=full keisei-battle-alpine:latest
|
||||
#
|
||||
# Known-issue: aws-sdk-s3 / rusqlite / git2 may fail to static-link on
|
||||
# musl. That IS what this matrix catches. Treat such failures as
|
||||
# "known-issue on musl", not as a blocker for this image.
|
||||
|
||||
FROM alpine:3.19
|
||||
|
||||
ENV LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
# Baseline deps — jq (HARD prereq), pandoc (soft), git/curl/ca-certs
|
||||
# (kit + optional rustup), build-base (cc/ld), rust+cargo (distro pkg —
|
||||
# Alpine 3.19 ships 1.76; edition = "2024" (1.85+) crates may fail, that
|
||||
# failure is part of the test surface).
|
||||
RUN apk add --no-cache \
|
||||
bash curl ca-certificates git build-base \
|
||||
jq pandoc \
|
||||
rust cargo
|
||||
|
||||
WORKDIR /opt/keiseikit
|
||||
COPY . /opt/keiseikit/
|
||||
|
||||
COPY tests/battle/verify.sh /usr/local/bin/verify.sh
|
||||
COPY tests/battle/battle-entry.sh /usr/local/bin/battle-entry.sh
|
||||
|
||||
RUN chmod +x /usr/local/bin/verify.sh \
|
||||
/usr/local/bin/battle-entry.sh \
|
||||
/opt/keiseikit/install.sh
|
||||
|
||||
ENV PROFILE=minimal
|
||||
ENTRYPOINT ["/usr/local/bin/battle-entry.sh"]
|
||||
46
tests/battle/Dockerfile.install-test-debian
Normal file
46
tests/battle/Dockerfile.install-test-debian
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# tests/battle/Dockerfile.install-test-debian
|
||||
# Debian 12 (bookworm) — the default server distro. glibc, different apt
|
||||
# repo structure from Ubuntu (no `ubuntu-*` meta-packages).
|
||||
#
|
||||
# Build from repo root:
|
||||
# docker build -t keisei-battle-debian:latest -f tests/battle/Dockerfile.install-test-debian .
|
||||
# Run (default: minimal profile):
|
||||
# docker run --rm keisei-battle-debian:latest
|
||||
# Override:
|
||||
# docker run --rm -e PROFILE=full keisei-battle-debian:latest
|
||||
|
||||
FROM debian:12
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
CARGO_HOME=/root/.cargo \
|
||||
RUSTUP_HOME=/root/.rustup \
|
||||
PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
# Baseline deps + rustup-installed stable toolchain. Debian 12 ships
|
||||
# rustc 1.63 (too old for edition = "2024"), so apt-install of rustc is
|
||||
# explicitly NOT enough — same quirk the Ubuntu Dockerfile documents.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash curl ca-certificates git build-essential \
|
||||
jq pandoc \
|
||||
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||
| sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path \
|
||||
&& ln -sf /root/.cargo/bin/cargo /usr/local/bin/cargo \
|
||||
&& ln -sf /root/.cargo/bin/rustc /usr/local/bin/rustc \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rustc --version && cargo --version
|
||||
|
||||
WORKDIR /opt/keiseikit
|
||||
COPY . /opt/keiseikit/
|
||||
|
||||
COPY tests/battle/verify.sh /usr/local/bin/verify.sh
|
||||
COPY tests/battle/battle-entry.sh /usr/local/bin/battle-entry.sh
|
||||
|
||||
RUN chmod +x /usr/local/bin/verify.sh \
|
||||
/usr/local/bin/battle-entry.sh \
|
||||
/opt/keiseikit/install.sh
|
||||
|
||||
ENV PROFILE=minimal
|
||||
ENTRYPOINT ["/usr/local/bin/battle-entry.sh"]
|
||||
|
|
@ -1,18 +1,47 @@
|
|||
# tests/battle — Clean-Ubuntu Install Test
|
||||
# tests/battle — Clean-Distro Install Test Matrix
|
||||
|
||||
Validates `install.sh` on a fresh `ubuntu:24.04` container. CI only runs
|
||||
`--no-execute` dry-runs; this battle-test actually executes the installer.
|
||||
Validates `install.sh` on fresh container images. CI only runs
|
||||
`--no-execute` dry-runs; these battle-tests actually execute the
|
||||
installer against real distro package sets.
|
||||
|
||||
## Run
|
||||
## Matrix (v0.22+)
|
||||
|
||||
| Image | libc | Dockerfile |
|
||||
|---|---|---|
|
||||
| `ubuntu:24.04` | glibc | `Dockerfile.install-test` (CI baseline) |
|
||||
| `alpine:3.19` | musl | `Dockerfile.install-test-alpine` |
|
||||
| `debian:12` | glibc | `Dockerfile.install-test-debian` |
|
||||
|
||||
`ubuntu` is the historic baseline. `alpine` exposes musl-static-link
|
||||
quirks in `rusqlite`, `git2`, `aws-sdk-s3` — crates that wrap C code and
|
||||
are known to behave differently under musl. `debian` covers the most
|
||||
common server deployment; its apt repo layout differs from Ubuntu, so a
|
||||
Debian pass rules out a "ubuntu-specific fix" regression.
|
||||
|
||||
## Run one image
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
docker build -t keisei-battle:latest -f tests/battle/Dockerfile.install-test .
|
||||
docker run --rm keisei-battle:latest # minimal
|
||||
docker run --rm -e PROFILE=core keisei-battle:latest
|
||||
docker run --rm -e PROFILE=dev keisei-battle:latest
|
||||
docker run --rm -e PROFILE=full keisei-battle:latest
|
||||
docker build -t keisei-battle-ubuntu -f tests/battle/Dockerfile.install-test .
|
||||
docker run --rm keisei-battle-ubuntu # minimal
|
||||
docker run --rm -e PROFILE=core keisei-battle-ubuntu
|
||||
docker run --rm -e PROFILE=dev keisei-battle-ubuntu
|
||||
docker run --rm -e PROFILE=full keisei-battle-ubuntu
|
||||
```
|
||||
|
||||
## Run the whole matrix
|
||||
|
||||
```bash
|
||||
for distro in ubuntu alpine debian; do
|
||||
if [ "$distro" = "ubuntu" ]; then
|
||||
DF=Dockerfile.install-test
|
||||
else
|
||||
DF=Dockerfile.install-test-$distro
|
||||
fi
|
||||
docker build -t keisei-battle-$distro -f tests/battle/$DF .
|
||||
docker run --rm -e PROFILE=full keisei-battle-$distro
|
||||
done
|
||||
```
|
||||
|
||||
Container exits 0 = green. Any other code = investigate stdout.
|
||||
|
|
@ -35,5 +64,12 @@ Container exits 0 = green. Any other code = investigate stdout.
|
|||
drops (`6/25` on full). Fix: copy every sibling dir the crate ships.
|
||||
- **Ubuntu 24.04 rustc is 1.75** — too old for `edition = "2024"`.
|
||||
Dockerfile installs rustup stable; `apt install rustc` is NOT enough.
|
||||
- **Apple Silicon hosts**: image builds linux/arm64 natively; binaries
|
||||
- **Debian 12 rustc is 1.63** — same story. Dockerfile uses rustup.
|
||||
- **Alpine 3.19 rustc is 1.76** — still below edition-2024's 1.85 floor;
|
||||
some primitives may fail to build. That failure IS what this image
|
||||
catches; document as "known-issue on musl" rather than patching here.
|
||||
- **Alpine musl + aws-sdk-s3 / rusqlite / git2**: static-link failures
|
||||
are EXPECTED on musl for the `s3` / SQLite-backed primitives. Treat
|
||||
as matrix signal, not regression.
|
||||
- **Apple Silicon hosts**: images build linux/arm64 natively; binaries
|
||||
produced inside won't run on x86_64 hosts.
|
||||
|
|
|
|||
Loading…
Reference in a new issue