177 lines
8.6 KiB
Bash
177 lines
8.6 KiB
Bash
|
|
#!/bin/sh
|
||
|
|
# Layer 0 — clawdie-live-seed importer regression test (runs on any POSIX host).
|
||
|
|
#
|
||
|
|
# Exercises the REAL importer (live/operator-session/clawdie-live-seed) against a
|
||
|
|
# synthetic CLAWDIESEED tree, with every path redirected to a temp sandbox via the
|
||
|
|
# SEED_* override vars and CLAWDIE_SEED_TEST=1 (which skips the rc.subr handoff).
|
||
|
|
# No FreeBSD, no mount, no root required — chowns fail silently by design.
|
||
|
|
#
|
||
|
|
# It encodes the seed->runtime propagation contract as assertions:
|
||
|
|
# - operator home: .env (app keys), vault-bootstrap.env (BW_*), ssh material
|
||
|
|
# - daemon home: outbound ssh material (mother-mcp key), NO authorized_keys
|
||
|
|
# - provider.env: app provider keys only (no BW_*)
|
||
|
|
# - staging: soul/ tree, harness.toml, agent-name, active-agent, raw env
|
||
|
|
# - idempotency: re-import does not duplicate keys / known_hosts
|
||
|
|
#
|
||
|
|
# PENDING group: AGENTS.md -> zot home. This is the change Hermes is pushing.
|
||
|
|
# It is xfail (informational) until the importer learns to install AGENTS.md;
|
||
|
|
# re-run with REQUIRE_AGENTS_MD=1 after the patch to enforce it as required.
|
||
|
|
#
|
||
|
|
# Usage: sh tests/seed-import-test.sh # current contract must pass
|
||
|
|
# REQUIRE_AGENTS_MD=1 sh tests/... # also enforce the AGENTS.md patch
|
||
|
|
set -u
|
||
|
|
|
||
|
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
|
|
IMPORTER="${IMPORTER:-${SCRIPT_DIR}/../live/operator-session/clawdie-live-seed}"
|
||
|
|
|
||
|
|
if [ ! -r "${IMPORTER}" ]; then
|
||
|
|
echo "FATAL: importer not found at ${IMPORTER}" >&2
|
||
|
|
exit 2
|
||
|
|
fi
|
||
|
|
|
||
|
|
WORK=$(mktemp -d "${TMPDIR:-/tmp}/seed-import-test.XXXXXX") || exit 2
|
||
|
|
trap 'rm -rf "${WORK}"' EXIT INT TERM
|
||
|
|
|
||
|
|
# --- override every path the importer touches into the sandbox ---------------
|
||
|
|
TEST_USER=$(id -un)
|
||
|
|
export CLAWDIE_SEED_TEST=1
|
||
|
|
export SEED_MOUNT="${WORK}/seed"
|
||
|
|
export SEED_LOG="${WORK}/seed.log"
|
||
|
|
export SEED_USER="${TEST_USER}"
|
||
|
|
export SEED_USER_HOME="${WORK}/operator-home"
|
||
|
|
export SEED_DAEMON_USER="${TEST_USER}"
|
||
|
|
export SEED_DAEMON_HOME="${WORK}/daemon-home"
|
||
|
|
export SEED_IMPORT_ROOT="${WORK}/import-root"
|
||
|
|
export SEED_PROVIDER_ENV="${WORK}/provider.env"
|
||
|
|
# Contract under test for the AGENTS.md patch: importer installs it to zot's
|
||
|
|
# global slot under the daemon home. Keep this in one place so it tracks the
|
||
|
|
# final decision (ZOT_HOME pin) without edits scattered through the test.
|
||
|
|
ZOT_HOME_REL=".local/state/zot"
|
||
|
|
EXPECT_AGENTS_MD="${SEED_DAEMON_HOME}/${ZOT_HOME_REL}/AGENTS.md"
|
||
|
|
|
||
|
|
mkdir -p "${SEED_USER_HOME}" "${SEED_DAEMON_HOME}" "${SEED_IMPORT_ROOT}"
|
||
|
|
|
||
|
|
# --- build a synthetic single-agent seed (the live-USB case) ------------------
|
||
|
|
SEED_AGENT="${SEED_MOUNT}/clawdie"
|
||
|
|
mkdir -p "${SEED_AGENT}/ssh" "${SEED_AGENT}/soul/memories"
|
||
|
|
|
||
|
|
cat >"${SEED_AGENT}/env" <<'EOF'
|
||
|
|
# operator-provided secrets (plaintext FAT32 by design)
|
||
|
|
DEEPSEEK_API_KEY=sk-test-deepseek
|
||
|
|
DEEPSEEK_MODEL=deepseek-v4
|
||
|
|
BW_CLIENTID=user.test-client
|
||
|
|
BW_CLIENTSECRET=test-secret
|
||
|
|
BW_PASSWORD=test-master-pw
|
||
|
|
EOF
|
||
|
|
|
||
|
|
cat >"${SEED_AGENT}/harness.toml" <<'EOF'
|
||
|
|
harness = "zot"
|
||
|
|
EOF
|
||
|
|
|
||
|
|
cat >"${SEED_AGENT}/AGENTS.md" <<'EOF'
|
||
|
|
# Operational rules (zot reads this as project context)
|
||
|
|
- mother is OSA, reachable via the mother-mcp key on this seed
|
||
|
|
- verbs: node_register, create-task, intake-task
|
||
|
|
- this node is a USB operator, capability: freebsd
|
||
|
|
- install Hermes from /home/clawdie/ai/hermes-bsd
|
||
|
|
CANARY_AGENTS_MARKER=zot-sees-this
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# soul tree (dormant; must be STAGED, not activated, in 0.12)
|
||
|
|
cat >"${SEED_AGENT}/soul/SOUL.md" <<'EOF'
|
||
|
|
# SOUL (staged for Hermes, dormant)
|
||
|
|
EOF
|
||
|
|
cat >"${SEED_AGENT}/soul/memories/USER.md" <<'EOF'
|
||
|
|
# USER (staged)
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# ssh material: inbound authorized_keys + outbound client (config, key, known_hosts)
|
||
|
|
echo "ssh-ed25519 AAAAINBOUNDtestkey operator@laptop" >"${SEED_AGENT}/ssh/authorized_keys"
|
||
|
|
cat >"${SEED_AGENT}/ssh/config" <<'EOF'
|
||
|
|
Host mother
|
||
|
|
HostName 100.72.229.63
|
||
|
|
User colibri
|
||
|
|
IdentityFile ~/.ssh/mother-mcp
|
||
|
|
EOF
|
||
|
|
echo "-----BEGIN OPENSSH PRIVATE KEY-----TEST-----END-----" >"${SEED_AGENT}/ssh/mother-mcp"
|
||
|
|
echo "ssh-ed25519 AAAAMOTHERPUBkey colibri@mother" >"${SEED_AGENT}/ssh/mother-mcp.pub"
|
||
|
|
echo "mother-host ssh-ed25519 AAAAMOTHERhostkey" >"${SEED_AGENT}/ssh/known_hosts"
|
||
|
|
|
||
|
|
# --- assertion harness -------------------------------------------------------
|
||
|
|
PASS=0
|
||
|
|
REQ_FAIL=0
|
||
|
|
PEND_FAIL=0
|
||
|
|
|
||
|
|
ok() { PASS=$((PASS+1)); printf ' ok %s\n' "$1"; }
|
||
|
|
fail() { REQ_FAIL=$((REQ_FAIL+1)); printf ' FAIL %s\n' "$1"; }
|
||
|
|
pend() { PEND_FAIL=$((PEND_FAIL+1)); printf ' PEND %s\n' "$1"; }
|
||
|
|
|
||
|
|
# fail() vs pend() chosen by tag arg ($1 = required|pending)
|
||
|
|
report() { # report <required|pending> <passed 0|1> <msg>
|
||
|
|
if [ "$2" -eq 1 ]; then ok "$3"
|
||
|
|
elif [ "$1" = pending ] && [ -z "${REQUIRE_AGENTS_MD:-}" ]; then pend "$3"
|
||
|
|
else fail "$3"; fi
|
||
|
|
}
|
||
|
|
|
||
|
|
exists() { [ -e "$2" ] && report "$1" 1 "$3" || report "$1" 0 "$3"; }
|
||
|
|
not_exists() { [ ! -e "$2" ] && report "$1" 1 "$3" || report "$1" 0 "$3"; }
|
||
|
|
contains() { [ -f "$2" ] && grep -q "$3" "$2" 2>/dev/null && report "$1" 1 "$4" || report "$1" 0 "$4"; }
|
||
|
|
absent_in() { { [ ! -f "$2" ] || ! grep -q "$3" "$2" 2>/dev/null; } && report "$1" 1 "$4" || report "$1" 0 "$4"; }
|
||
|
|
count_is() { _c=$(grep -c "$3" "$2" 2>/dev/null || echo 0); [ "$_c" = "$4" ] && report "$1" 1 "$5 (got $_c)" || report "$1" 0 "$5 (got $_c)"; }
|
||
|
|
|
||
|
|
# --- run the real importer ---------------------------------------------------
|
||
|
|
# shellcheck disable=SC1090
|
||
|
|
. "${IMPORTER}"
|
||
|
|
echo "== import pass 1 =="
|
||
|
|
_seed_import_tree
|
||
|
|
|
||
|
|
OP_HOME="${SEED_USER_HOME}"
|
||
|
|
DM_HOME="${SEED_DAEMON_HOME}"
|
||
|
|
STAGE="${SEED_IMPORT_ROOT}/clawdie"
|
||
|
|
|
||
|
|
echo "-- operator home --"
|
||
|
|
contains required "${OP_HOME}/.env" '^DEEPSEEK_API_KEY=sk-test-deepseek' "DEEPSEEK key in operator ~/.env"
|
||
|
|
absent_in required "${OP_HOME}/.env" '^BW_PASSWORD=' "BW_* NOT in operator ~/.env"
|
||
|
|
contains required "${OP_HOME}/.config/vault-bootstrap.env" '^BW_PASSWORD=test-master-pw' "BW_PASSWORD in vault-bootstrap.env"
|
||
|
|
exists required "${OP_HOME}/.ssh/authorized_keys" "operator authorized_keys installed"
|
||
|
|
exists required "${OP_HOME}/.ssh/config" "operator ssh config installed"
|
||
|
|
exists required "${OP_HOME}/.ssh/mother-mcp" "operator mother-mcp private key installed"
|
||
|
|
contains required "${OP_HOME}/.ssh/known_hosts" 'mother-host' "operator known_hosts has mother host key"
|
||
|
|
|
||
|
|
echo "-- daemon home (colibri spawns the outbound MCP SSH) --"
|
||
|
|
exists required "${DM_HOME}/.ssh/mother-mcp" "daemon mother-mcp private key installed"
|
||
|
|
exists required "${DM_HOME}/.ssh/config" "daemon ssh config installed"
|
||
|
|
not_exists required "${DM_HOME}/.ssh/authorized_keys" "daemon has NO authorized_keys (inbound is operator-only)"
|
||
|
|
|
||
|
|
echo "-- provider.env (daemon autospawn keys; root-owned) --"
|
||
|
|
contains required "${SEED_PROVIDER_ENV}" '^DEEPSEEK_API_KEY=sk-test-deepseek' "DEEPSEEK key in provider.env"
|
||
|
|
absent_in required "${SEED_PROVIDER_ENV}" '^BW_PASSWORD=' "BW_* NOT in provider.env"
|
||
|
|
|
||
|
|
echo "-- staging (dormant payload) --"
|
||
|
|
exists required "${STAGE}/soul/SOUL.md" "soul/ tree staged"
|
||
|
|
exists required "${STAGE}/soul/memories/USER.md" "soul memories staged"
|
||
|
|
contains required "${STAGE}/harness.toml" 'harness = "zot"' "harness.toml recorded"
|
||
|
|
contains required "${STAGE}/agent-name" '^clawdie$' "agent-name staged"
|
||
|
|
contains required "${SEED_IMPORT_ROOT}/active-agent" '^clawdie$' "active-agent recorded"
|
||
|
|
exists required "${STAGE}/env" "raw env staged (0600)"
|
||
|
|
|
||
|
|
echo "-- PENDING: AGENTS.md -> zot global slot (Hermes's importer patch) --"
|
||
|
|
exists pending "${EXPECT_AGENTS_MD}" "AGENTS.md installed to \$ZOT_HOME"
|
||
|
|
contains pending "${EXPECT_AGENTS_MD}" 'CANARY_AGENTS_MARKER=zot-sees-this' "AGENTS.md content intact at zot slot"
|
||
|
|
|
||
|
|
# --- idempotency: a second import must not duplicate -------------------------
|
||
|
|
echo "== import pass 2 (idempotency) =="
|
||
|
|
_seed_import_tree
|
||
|
|
count_is required "${OP_HOME}/.env" '^DEEPSEEK_API_KEY=' 1 "exactly one DEEPSEEK key in ~/.env after re-import"
|
||
|
|
count_is required "${SEED_PROVIDER_ENV}" '^DEEPSEEK_API_KEY=' 1 "exactly one DEEPSEEK key in provider.env after re-import"
|
||
|
|
count_is required "${OP_HOME}/.ssh/known_hosts" 'mother-host' 1 "known_hosts not duplicated after re-import"
|
||
|
|
|
||
|
|
# --- summary -----------------------------------------------------------------
|
||
|
|
echo
|
||
|
|
echo "RESULT: ${PASS} passed, ${REQ_FAIL} required-fail, ${PEND_FAIL} pending"
|
||
|
|
if [ -n "${REQUIRE_AGENTS_MD:-}" ] && [ "${PEND_FAIL}" -gt 0 ]; then
|
||
|
|
echo " (REQUIRE_AGENTS_MD set: pending failures count as required)"
|
||
|
|
fi
|
||
|
|
[ "${REQ_FAIL}" -eq 0 ] || exit 1
|
||
|
|
exit 0
|