Files
Panama/tests/setup/opencode-config-contract
T

160 lines
7.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# The opencode config, generated rather than linked.
#
# Every other personal destination is a symlink or a one-time copy, so getting
# one wrong is visible and cheap to undo. This one is written whole by
# setup/scripts/link-mcp, and it is the only file in the set that carries
# secrets -- which is the reason it is generated at all. Three properties are
# worth pinning:
#
# 1. The tracked template is the source of truth and holds no secret. It is
# readable by anyone who finds the repository, so a token in it is a
# disclosure rather than a mistake.
# 2. The generation is faithful: the template's keys survive, the MCP block
# appears, and a leading ~/ is resolved so the output is readable.
# 3. Nothing is destroyed and nothing churns. A file somebody wrote by hand
# goes to config/old/, and running the stage again does not fill it up.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
linker="$repo_dir/setup/scripts/link-mcp"
template="$repo_dir/user/agents/opencode/config.json"
servers="$repo_dir/user/agents/mcp/servers"
findings=()
note() { findings+=("$1"); }
[[ -x "$linker" ]] || { printf 'opencode config contract: %s is not executable\n' "$linker" >&2; exit 1; }
# ── 1. The template holds no secret ──────────────────────────────────────────
if [[ ! -r "$template" ]]; then
note 'user/agents/opencode/config.json is missing, so nothing generates an opencode config'
else
jq -e . "$template" >/dev/null 2>&1 \
|| note 'user/agents/opencode/config.json is not valid JSON'
if grep -qE 'BEGIN [A-Z ]*PRIVATE KEY|sk-ant-[A-Za-z0-9]|ghp_[A-Za-z0-9]{20}|xox[baprs]-[A-Za-z0-9]' "$template" 2>/dev/null; then
note 'user/agents/opencode/config.json carries something that looks like a token'
fi
fi
[[ -r "$servers" ]] || note 'user/agents/mcp/servers is missing, so no MCP servers can be registered'
# ── 2 and 3, against a throwaway home ────────────────────────────────────────
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
checkout="$work/Panama"
mkdir -p "$checkout/setup/scripts" "$checkout/user/agents/mcp/bin" "$checkout/user/agents/opencode"
cp "$linker" "$checkout/setup/scripts/link-mcp"
cp "$template" "$checkout/user/agents/opencode/config.json"
printf '#!/usr/bin/env bash\nexit 0\n' >"$checkout/user/agents/mcp/bin/gamma"
chmod +x "$checkout/user/agents/mcp/bin/gamma"
cat >"$checkout/user/agents/mcp/servers" <<'FIXTURE'
# a comment, then a row that needs a token, one that needs none, and a stdio one
alpha http https://alpha.example/mcp ALPHA_TOKEN
beta http https://beta.example/mcp
gamma stdio bin/gamma
FIXTURE
printf 'ALPHA_TOKEN=sekrit-token-value\n' >"$checkout/user/agents/mcp/env"
# link-mcp registers through whichever runtime it finds, and gives up entirely
# when it finds neither. A stub is enough to prove the config generation.
mkdir -p "$work/bin"
printf '#!/usr/bin/env bash\nexit 0\n' >"$work/bin/opencode"
chmod +x "$work/bin/opencode"
home="$work/home"
state="$work/state"
old="$checkout/config/old"
# Every XDG directory is pinned to the throwaway home. XDG_CONFIG_HOME is
# usually set in the environment this runs from, and leaving it alone would send
# the generated config at the real ~/.config/opencode rather than at the fixture.
mkdir -p "$home/.config/opencode" "$state/panama"
run() {
HOME="$home" XDG_CONFIG_HOME="$home/.config" XDG_STATE_HOME="$state" \
PANAMA_PATH="$checkout" PATH="$work/bin:$PATH" \
"$checkout/setup/scripts/link-mcp" >"$work/log" 2>&1
}
generated="$home/.config/opencode/opencode.jsonc"
# A file somebody wrote by hand is there before this stage ever runs.
printf '{"model": "someone-elses-hand-edit"}\n' >"$generated"
printf 'yes\n' >"$state/panama/user-content"
if ! run; then
note "link-mcp failed against a throwaway home: $(tail -1 "$work/log")"
fi
# ── The hand-edit is preserved, not deleted ──────────────────────────────────
if ! grep -rq 'someone-elses-hand-edit' "$old" 2>/dev/null; then
note 'a config written by hand was not preserved in config/old/'
fi
# ── The generation is faithful ───────────────────────────────────────────────
body="$(sed '/^\/\//d' "$generated" 2>/dev/null)"
jq -e . <<<"$body" >/dev/null 2>&1 \
|| note 'the generated opencode config is not valid JSON once its header comment is stripped'
grep -qF 'generated by setup/scripts/link-mcp' "$generated" 2>/dev/null \
|| note 'the generated opencode config does not say who wrote it or where to edit instead'
if ! jq -e --arg home "$home" '.instructions | index($home + "/.claude/rules/*.md")' <<<"$body" >/dev/null 2>&1; then
note 'the generated opencode config did not resolve the ~/ rules path to this home'
fi
jq -e '.permissions | length > 0' <<<"$body" >/dev/null 2>&1 \
|| note 'the template permissions did not reach the generated opencode config'
jq -e '.mcp.alpha.headers.Authorization == "sekrit-token-value"' <<<"$body" >/dev/null 2>&1 \
|| note 'a token-carrying server was registered without its Authorization header'
jq -e '.mcp.beta | has("headers") | not' <<<"$body" >/dev/null 2>&1 \
|| note 'a server that needs no token was given a header anyway'
jq -e --arg launcher "$checkout/user/agents/mcp/bin/gamma" '.mcp.gamma.command == [$launcher]' <<<"$body" >/dev/null 2>&1 \
|| note 'a stdio server was not registered against its launcher in this checkout'
# The file holds tokens, so it is private. Checked on the file rather than on
# the umask, because the umask is not what a later reader sees.
mode="$(stat -c '%a' "$generated" 2>/dev/null)"
[[ "$mode" == 600 ]] || note "the generated opencode config is mode $mode, not 600"
# The template is readable by anybody who finds the repository. The tokens live
# in the generated file and in user/agents/mcp/env, never here.
if grep -qF 'sekrit-token-value' "$template" 2>/dev/null; then
note 'the tracked opencode template carries an MCP token'
fi
# ── Re-running replaces, and does not churn ──────────────────────────────────
before="$(find "$old" -type f 2>/dev/null | wc -l)"
run
after="$(find "$old" -type f 2>/dev/null | wc -l)"
[[ "$before" == "$after" ]] \
|| note 're-running link-mcp buried its own output in config/old/, so it grows every upgrade'
grep -qF 'generated by setup/scripts/link-mcp' "$generated" 2>/dev/null \
|| note 're-running link-mcp did not rewrite the config it generates'
# A machine that never said yes is left alone, the same gate every other
# personal destination obeys.
rm -f "$generated"
printf 'no\n' >"$state/panama/user-content"
run
[[ ! -e "$generated" ]] \
|| note 'an opencode config was generated on a machine that never said yes'
if (( ${#findings[@]} > 0 )); then
printf 'opencode config contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2
exit 1
fi
printf 'opencode config contract: PASS\n'