Merge test/v0.21-battle-docker — Docker install.sh battle-test (CHANGELOG conflict resolved)

This commit is contained in:
Parfii-bot 2026-04-22 18:38:58 +08:00
commit 82689d108b
5 changed files with 175 additions and 0 deletions

View file

@ -33,6 +33,7 @@ _primitives/_rust/target/release/kei-changelog \
- "Branch" semantics: S3 has no native branching, so a branch is modelled as a key prefix (`<branch>/<path>`). `branch()` sets the active prefix in-memory; default `main`.
- Factory auto-routes: `backend = "s3"` + feature `s3` + `s3.bucket` set → real cloud; otherwise falls back to the v0.14 local-manifest stub (still behind `KEI_STORE_ALLOW_S3_STUB=1`).
- Path-traversal guard parity with `FilesystemStore`: absolute and `..`-component paths rejected before keys are spliced.
- **tests/battle:** Docker-based clean-Ubuntu install test — `tests/battle/Dockerfile.install-test` + `verify.sh` + `battle-entry.sh` + README. Builds a fresh `ubuntu:24.04` image, runs `install.sh --profile=<minimal|core|dev|full>` under `--yes`, then asserts post-install counts (blocks ≥ 79, skills ≥ 39, top hooks ≥ 10, `_lib` hooks ≥ 2), runs `hooks/_lib/test-gate.sh`, and validates `settings.json`. First real-world "does it work on a fresh machine?" signal — CI previously only ran `--no-execute` dry-runs. v0.21 ship-blocker for any profile that regresses.
- **primitives (v0.20 — brain schema v2 + per-client hint):**
- Brain schema v2 with per-platform `mcp_server` dispatch — a single brain directory can now host binaries for darwin-arm64/darwin-x64/linux-x64/linux-arm64/windows-x64 and `keisei attach` picks the right one automatically. Schema v1 (single string) still accepted for backward-compat.
- `ClientAdapter::post_attach_hint()` — per-client reload instruction, no more hardcoded Claude-Code string in the orchestrator.

View file

@ -0,0 +1,57 @@
# tests/battle/Dockerfile.install-test
# Battle-test: run KeiSeiKit install.sh on a clean Ubuntu 24.04 container.
# Validates: install succeeds, block/skill/hook counts match README, hook
# test-gate passes, settings.json (if any) is valid JSON.
#
# Build from repo root:
# docker build -t keisei-battle:latest -f tests/battle/Dockerfile.install-test .
#
# Run (default: minimal profile):
# docker run --rm keisei-battle:latest
#
# Override profile:
# docker run --rm -e PROFILE=dev keisei-battle:latest
# docker run --rm -e PROFILE=full keisei-battle:latest
#
# Env:
# PROFILE one of minimal|core|dev|full (default: minimal)
# KEI_SKIP_RUST_BUILD 1 = skip cargo build for primitives (assembler
# still builds; install.sh always builds it)
FROM ubuntu:24.04
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 that a "fresh" user machine would have after apt install.
# build-essential: cc/ld for cargo. jq: install.sh HARD prereq. pandoc:
# soft prereq for tomd. git, curl, ca-certificates: kit + rustup download.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git curl ca-certificates build-essential jq pandoc \
&& rm -rf /var/lib/apt/lists/*
# Rust toolchain — Ubuntu 24.04 ships rustc 1.75; _assembler/Cargo.toml uses
# edition = "2024" which needs >= 1.85. Use rustup to get stable.
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path \
&& rustc --version && cargo --version
# Copy the kit. .dockerignore excludes target/ so the image stays slim.
WORKDIR /opt/keiseikit
COPY . /opt/keiseikit/
# Install verify + entry scripts (path is hardcoded so they remain runnable
# whatever CWD the user sets via -w).
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"]

39
tests/battle/README.md Normal file
View file

@ -0,0 +1,39 @@
# tests/battle — Clean-Ubuntu Install Test
Validates `install.sh` on a fresh `ubuntu:24.04` container. CI only runs
`--no-execute` dry-runs; this battle-test actually executes the installer.
## Run
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
```
Container exits 0 = green. Any other code = investigate stdout.
## What it asserts (verify.sh)
- `~/.claude/agents/_blocks` ≥ 79
- `~/.claude/skills` ≥ 39
- `~/.claude/hooks/*.sh` ≥ 10 top-level
- `~/.claude/hooks/_lib/*.sh` ≥ 2 (gate.sh + test-gate.sh, v0.17)
- `hooks/_lib/test-gate.sh` self-test passes (11/11)
- `settings.json` (if created) parses as valid JSON
## Known quirks (2026-04-22)
- **`kei-artifact` crate fails** on `dev`/`full`: `copy_rust_primitive`
(install/lib-primitives.sh) copies `src/` + `tests/` only — misses
sibling `schemas/`, so `include_str!("../schemas/*.json")` breaks.
Install still exits 0 (build is soft-fail); primitive binary count
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
produced inside won't run on x86_64 hosts.

28
tests/battle/battle-entry.sh Executable file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# tests/battle/battle-entry.sh — container ENTRYPOINT.
# Picks profile from $PROFILE env (default: minimal), runs installer, then
# verify.sh. Kept as a dedicated file (instead of a Dockerfile heredoc) so
# BuildKit isn't required and the script is editable post-image-build.
set -u
PROFILE="${PROFILE:-minimal}"
echo "=== battle-test: profile=$PROFILE ==="
echo "=== host: $(uname -a) ==="
echo "=== cargo: $(cargo --version) ==="
echo "=== jq: $(jq --version) ==="
echo
cd /opt/keiseikit || { echo "kit missing at /opt/keiseikit"; exit 2; }
./install.sh --profile="$PROFILE" --yes 2>&1
INSTALL_EXIT=$?
echo
echo "=== install exit code: $INSTALL_EXIT ==="
if [ "$INSTALL_EXIT" -ne 0 ]; then
echo "=== install failed; skipping verify ==="
exit "$INSTALL_EXIT"
fi
echo
echo "=== running verify.sh ==="
/usr/local/bin/verify.sh

50
tests/battle/verify.sh Executable file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env sh
# tests/battle/verify.sh — post-install assertions for the battle test.
# POSIX sh; runs inside ubuntu:24.04 container as root ($HOME=/root).
# Thresholds match v0.21 kit snapshot (2026-04-22):
# _blocks >= 79, skills >= 39, top hooks >= 10, _lib hooks >= 2.
set -u
fail() { printf 'FAIL: %s\n' "$1" >&2; exit 1; }
pass() { printf 'PASS: %s\n' "$1"; }
AG="$HOME/.claude/agents"
HK="$HOME/.claude/hooks"
SK="$HOME/.claude/skills"
n_blocks=$(ls -1 "$AG/_blocks" 2>/dev/null | wc -l | tr -d ' ')
[ "$n_blocks" -ge 79 ] || fail "_blocks count $n_blocks < 79"
pass "_blocks count = $n_blocks (>= 79)"
n_skills=$(ls -1 "$SK" 2>/dev/null | wc -l | tr -d ' ')
[ "$n_skills" -ge 39 ] || fail "skills count $n_skills < 39"
pass "skills count = $n_skills (>= 39)"
n_hooks=$(find "$HK" -maxdepth 1 -type f -name '*.sh' 2>/dev/null | wc -l | tr -d ' ')
[ "$n_hooks" -ge 10 ] || fail "top hooks count $n_hooks < 10"
pass "top hooks count = $n_hooks (>= 10)"
n_lib=$(find "$HK/_lib" -maxdepth 1 -type f -name '*.sh' 2>/dev/null | wc -l | tr -d ' ')
[ "$n_lib" -ge 2 ] || fail "_lib hooks count $n_lib < 2"
pass "_lib hooks count = $n_lib (>= 2)"
if [ -x "$HK/_lib/test-gate.sh" ]; then
if bash "$HK/_lib/test-gate.sh" >/tmp/test-gate.out 2>&1; then
pass "test-gate.sh exits 0"
else
cat /tmp/test-gate.out >&2
fail "test-gate.sh exited non-zero"
fi
else
fail "test-gate.sh missing at $HK/_lib/test-gate.sh"
fi
if [ -f "$HOME/.claude/settings.json" ]; then
jq . "$HOME/.claude/settings.json" >/dev/null 2>&1 \
&& pass "settings.json parses as valid JSON" \
|| fail "settings.json is not valid JSON"
else
pass "settings.json absent (not activated — OK)"
fi
echo "=== verify.sh: all checks passed ==="