Resolve security audit findings

ober

0c7e3927eb31ff4e148337fab613a92d46390d3b

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 723ae40..57db833 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,9 +13,9 @@ jobs:
   verify-private:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
 
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
         with:
           python-version: "3.12"
 
diff --git a/Makefile b/Makefile
index 13d5740..18a0078 100644
--- a/Makefile
+++ b/Makefile
@@ -5,8 +5,9 @@ EVIDENCE_DIR ?= dist/release-evidence
 PROVENANCE_DIR ?= dist/provenance
 SBOM_DIR ?= dist/sbom
 REPRO_DIR ?= dist/reproducibility
+TARGET_EVIDENCE_DIR ?= dist/target-evidence
 
-.PHONY: security test provenance-evidence sbom reproducibility-report verify release-evidence clean
+.PHONY: security test provenance-evidence sbom reproducibility-report target-evidence verify release-evidence clean
 
 security:
 	sh scripts/security-check.sh
@@ -14,6 +15,7 @@ security:
 test:
 	files="$$(git ls-files --cached --others --exclude-standard '*.py')"; \
 	if [ -n "$$files" ]; then $(PYTHON) -m py_compile $$files; fi
+	$(PYTHON) -m unittest discover -s tests -p 'test_*.py' -v
 
 provenance-evidence:
 	JLORA_PROVENANCE_DIR="$(PROVENANCE_DIR)" \
@@ -30,17 +32,21 @@ reproducibility-report:
 	JLORA_REPRO_DIR="$(REPRO_DIR)" \
 	sh scripts/reproducibility-report.sh
 
-verify: security test provenance-evidence sbom reproducibility-report
+target-evidence:
+	JLORA_TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh
+
+verify: security test provenance-evidence sbom reproducibility-report target-evidence
 	grep -q '^status=match$$' "$(REPRO_DIR)/report.txt"
 
 release-evidence:
-	rm -rf $(EVIDENCE_DIR) $(PROVENANCE_DIR) $(SBOM_DIR) $(REPRO_DIR)
+	rm -rf $(EVIDENCE_DIR) $(PROVENANCE_DIR) $(SBOM_DIR) $(REPRO_DIR) $(TARGET_EVIDENCE_DIR)
 	mkdir -p $(EVIDENCE_DIR)
 	$(MAKE) security >$(EVIDENCE_DIR)/security.log 2>&1
 	$(MAKE) test >$(EVIDENCE_DIR)/python-syntax.log 2>&1
 	$(MAKE) provenance-evidence >$(EVIDENCE_DIR)/provenance-evidence.log 2>&1
 	$(MAKE) sbom >$(EVIDENCE_DIR)/sbom.log 2>&1
 	$(MAKE) reproducibility-report >$(EVIDENCE_DIR)/reproducibility.log 2>&1
+	$(MAKE) target-evidence >$(EVIDENCE_DIR)/target-evidence.log 2>&1
 	grep -q '^status=match$$' "$(REPRO_DIR)/report.txt"
 	git rev-parse HEAD >$(EVIDENCE_DIR)/git-commit.txt
 	git status --short --ignored >$(EVIDENCE_DIR)/git-status-ignored.txt
@@ -48,13 +54,15 @@ release-evidence:
 	git ls-files >$(EVIDENCE_DIR)/tracked-files.txt
 	git ls-files --cached --others --exclude-standard >$(EVIDENCE_DIR)/working-files.txt
 	git status --ignored --short | sed -n '/^!!/p' >$(EVIDENCE_DIR)/ignored-artifacts.txt
-	git ls-files --cached --others --exclude-standard | grep -E '(\.py|\.sh|\.yaml|\.toml|\.json|\.md|Modelfile|LICENSE|SECURITY.md)$$' | xargs shasum -a 256 >$(EVIDENCE_DIR)/source-sha256.txt
+	git ls-files --cached --others --exclude-standard | grep -E '(\.py|\.sh|\.yaml|\.toml|\.json|\.tsv|\.md|Modelfile|LICENSE|SECURITY.md)$$' | xargs shasum -a 256 >$(EVIDENCE_DIR)/source-sha256.txt
 	rm -rf $(EVIDENCE_DIR)/provenance
 	cp -R $(PROVENANCE_DIR) $(EVIDENCE_DIR)/provenance
 	rm -rf $(EVIDENCE_DIR)/sbom
 	cp -R $(SBOM_DIR) $(EVIDENCE_DIR)/sbom
 	rm -rf $(EVIDENCE_DIR)/reproducibility
 	cp -R $(REPRO_DIR) $(EVIDENCE_DIR)/reproducibility
+	rm -rf $(EVIDENCE_DIR)/target-evidence
+	cp -R $(TARGET_EVIDENCE_DIR) $(EVIDENCE_DIR)/target-evidence
 	{ echo "repo=jerboa-lora"; echo "platform=$$(uname -srm)"; $(PYTHON) --version; } >$(EVIDENCE_DIR)/build-env.txt
 	sh scripts/sanitize-evidence.sh "$(EVIDENCE_DIR)"
 
diff --git a/SECURITY.md b/SECURITY.md
index 2c859ba..2477c2a 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -34,6 +34,14 @@ does not authorize public release. Public release remains blocked until dataset
 licenses, base-model licenses, generated outputs, private account references,
 and history secrets are reviewed.
 
+The supported v6 path reads `security/training-lock.json`, uses a digest-pinned
+OCI image and exact base-model commit, verifies all 47 model files against
+`security/model-files.lock.tsv`, and refuses remote model code. Fetch credentials
+are removed before training; training runs offline in a new network namespace.
+If the target cannot create that namespace, training fails closed. Legacy
+RunPod/vLLM launchers that exposed unauthenticated inference or logs are retired
+stubs, and local MLX launchers permit loopback binds only.
+
 `make provenance-evidence` records private model/data cards, source/data/config
 hashes, local model artifact inventory, and split-review proof status. Required
 split proof fails closed unless it includes reviewed base-model, dataset, model
@@ -48,6 +56,10 @@ card, dataset card, source split, and weight-publication markers.
   license clearance.
 - Do not publish model outputs from this repo without a model card, dataset
   card, safety/eval report, and redistribution decision.
+- API credentials belong in owner-only files or environment variables. Never
+  place them in URLs or argv, and never print generated credential-bearing
+  configuration. `configure_opencode.py` writes atomically with mode 0600 and
+  prints only a redacted status line.
 
 ## Reporting
 
diff --git a/axolotl_jerboa_v6_cpt.yaml b/axolotl_jerboa_v6_cpt.yaml
index 4957861..db6610d 100644
--- a/axolotl_jerboa_v6_cpt.yaml
+++ b/axolotl_jerboa_v6_cpt.yaml
@@ -3,9 +3,10 @@
 # Invariant: this file trains a PEFT adapter only. Do not add merge settings.
 
 base_model: __V6_BASE__
+revision: __V6_REVISION__
 model_type: AutoModelForCausalLM
 tokenizer_type: AutoTokenizer
-trust_remote_code: true
+trust_remote_code: false
 
 load_in_4bit: true
 adapter: qlora
diff --git a/axolotl_jerboa_v6_dpo.yaml b/axolotl_jerboa_v6_dpo.yaml
index 8b71de5..de01137 100644
--- a/axolotl_jerboa_v6_dpo.yaml
+++ b/axolotl_jerboa_v6_dpo.yaml
@@ -4,9 +4,10 @@
 # adapter. Do not fuse this adapter into the base for the v6 artifact.
 
 base_model: __V6_BASE__
+revision: __V6_REVISION__
 model_type: AutoModelForCausalLM
 tokenizer_type: AutoTokenizer
-trust_remote_code: true
+trust_remote_code: false
 
 load_in_4bit: true
 adapter: qlora
diff --git a/axolotl_jerboa_v6_sft.yaml b/axolotl_jerboa_v6_sft.yaml
index 86ce619..dc6da01 100644
--- a/axolotl_jerboa_v6_sft.yaml
+++ b/axolotl_jerboa_v6_sft.yaml
@@ -4,9 +4,10 @@
 # The release artifact remains an adapter directory, not fused weights.
 
 base_model: __V6_BASE__
+revision: __V6_REVISION__
 model_type: AutoModelForCausalLM
 tokenizer_type: AutoTokenizer
-trust_remote_code: true
+trust_remote_code: false
 
 load_in_4bit: true
 adapter: qlora
diff --git a/build_ollama_gguf.sh b/build_ollama_gguf.sh
index 1e17784..bc45c2c 100755
--- a/build_ollama_gguf.sh
+++ b/build_ollama_gguf.sh
@@ -1,198 +1,5 @@
-#!/bin/bash
-# Convert the merged Jerboa Qwen3-Coder-30B-A3B model to GGUF and register
-# it as Ollama models, ready for `ollama push`.
-#
-# Two output tags:
-#   jerboa-qwen:q8_0    — ~32GB, indistinguishable from F16 (the upload tag,
-#                         also aliased as "latest" so it's what users get by
-#                         default).
-#   jerboa-qwen:q4_k_m  — ~17GB, smaller; some loss of fine-tune signal but
-#                         runs comfortably on smaller machines.
-#
-# Disk usage during build (peak): ~115GB. Final (after cleanup): ~50GB.
-# Override with:
-#   KEEP_F16=1       keep the intermediate F16 GGUF (~60GB)
-#   QUANTS="q8_0"    build only specific quants (space-separated)
-#   CTX=131072       Modelfile num_ctx (default 32768)
-#
-# Usage:
-#   ./build_ollama_gguf.sh
+#!/bin/sh
+set -eu
 
-set -euo pipefail
-cd "$(dirname "$0")"
-
-SRC="${SRC:-./runpod-pipeline-final}"
-LLAMA_CPP="${LLAMA_CPP:-./llama.cpp}"
-OUT_DIR="${OUT_DIR:-./gguf}"
-F16_GGUF="$OUT_DIR/jerboa-qwen3.f16.gguf"
-MODEL_NAME="${MODEL_NAME:-jerboa-qwen}"
-QUANTS="${QUANTS:-q8_0 q4_k_m}"
-CTX="${CTX:-32768}"
-KEEP_F16="${KEEP_F16:-0}"
-
-echo "=== Build Jerboa Ollama GGUFs ==="
-echo "  Source:    $SRC"
-echo "  Output:    $OUT_DIR"
-echo "  Quants:    $QUANTS"
-echo "  Model:     $MODEL_NAME"
-echo "  Context:   $CTX"
-echo ""
-
-# ── Sanity ──────────────────────────────────────────────────────────────────
-if [ ! -d "$SRC" ]; then
-    echo "ERROR: source model dir '$SRC' not found." >&2
-    exit 1
-fi
-if ! command -v ollama &>/dev/null; then
-    echo "ERROR: 'ollama' not found. Install from https://ollama.com" >&2
-    exit 1
-fi
-mkdir -p "$OUT_DIR"
-
-# ── Step 1: llama.cpp (clone + build with Metal) ────────────────────────────
-echo "=== Step 1: llama.cpp ==="
-if [ ! -d "$LLAMA_CPP/.git" ]; then
-    echo "Cloning llama.cpp (full, need build) ..."
-    git clone --depth 1 https://github.com/ggml-org/llama.cpp.git "$LLAMA_CPP"
-fi
-
-# Verify Qwen3 MoE support
-if ! grep -q -i 'qwen3.*moe\|qwen3moe' "$LLAMA_CPP/convert_hf_to_gguf.py"; then
-    echo "WARNING: this llama.cpp checkout may not support Qwen3 MoE." >&2
-    echo "Consider 'cd $LLAMA_CPP && git pull' to refresh." >&2
-fi
-
-if [ ! -x "$LLAMA_CPP/build/bin/llama-quantize" ]; then
-    echo "Building llama.cpp (Metal) ..."
-    cmake -S "$LLAMA_CPP" -B "$LLAMA_CPP/build" -DGGML_METAL=ON -DLLAMA_CURL=OFF >/dev/null
-    cmake --build "$LLAMA_CPP/build" --config Release --target llama-quantize -j
-fi
-QUANTIZE="$LLAMA_CPP/build/bin/llama-quantize"
-
-# Python deps for convert_hf_to_gguf.py
-if ! python3 -c "import gguf, transformers, torch, safetensors" 2>/dev/null; then
-    echo "Installing Python deps for HF→GGUF converter ..."
-    python3 -m pip install --break-system-packages gguf transformers safetensors \
-        torch --index-url https://download.pytorch.org/whl/cpu || \
-    python3 -m pip install --break-system-packages gguf transformers safetensors torch
-fi
-
-# ── Step 2: HF merged → F16 GGUF ────────────────────────────────────────────
-echo ""
-echo "=== Step 2: HF → F16 GGUF (intermediate, ~60GB) ==="
-if [ -f "$F16_GGUF" ]; then
-    echo "Already exists: $F16_GGUF"
-else
-    python3 "$LLAMA_CPP/convert_hf_to_gguf.py" \
-        "$SRC" \
-        --outfile "$F16_GGUF" \
-        --outtype f16
-    echo "Wrote $F16_GGUF ($(du -h "$F16_GGUF" | cut -f1))"
-fi
-
-# ── Step 3: F16 → quantized variants ────────────────────────────────────────
-echo ""
-echo "=== Step 3: Quantize ==="
-gguf_path_for() { echo "$OUT_DIR/jerboa-qwen3.$1.gguf"; }
-for q in $QUANTS; do
-    Q_UPPER=$(echo "$q" | tr 'a-z' 'A-Z')
-    OUT="$(gguf_path_for "$q")"
-    if [ -f "$OUT" ]; then
-        echo "  $q: already exists ($(du -h "$OUT" | cut -f1))"
-    else
-        echo "  $q: quantizing ..."
-        "$QUANTIZE" "$F16_GGUF" "$OUT" "$Q_UPPER"
-        echo "  $q: wrote $OUT ($(du -h "$OUT" | cut -f1))"
-    fi
-done
-
-# ── Step 4: Modelfiles + ollama create ──────────────────────────────────────
-echo ""
-echo "=== Step 4: Register Ollama models ==="
-
-SYSTEM_PROMPT='You are an expert in Jerboa Scheme, a Chez-Scheme-based dialect with a Gerbil-flavored prelude. You provide accurate, idiomatic Jerboa code with correct imports, function names, and arities. Module paths use the (jerboa ...) and (std ...) forms — never :std/foo (Gerbil) or (srfi :NN) (R7). You know the prelude, the actor system, fibers, the FFI, capability security, the macro system (defrules, syntax-case), pattern matching (match), and how Jerboa diverges from Gerbil/Racket/Clojure/SRFI. When writing code, always include required (import ...) statements.'
-
-for q in $QUANTS; do
-    GGUF="$(gguf_path_for "$q")"
-    MF="$OUT_DIR/Modelfile.$q"
-    cat > "$MF" <<EOF
-FROM $(cd "$(dirname "$GGUF")" && pwd)/$(basename "$GGUF")
-
-SYSTEM "$SYSTEM_PROMPT"
-
-TEMPLATE """{{- if or .System .Tools }}<|im_start|>system
-{{- if .System }}
-{{ .System }}
-{{- end }}
-{{- if .Tools }}
-
-# Tools
-
-You may call one or more functions to assist with the user query.
-
-You are provided with function signatures within <tools></tools> XML tags:
-<tools>
-{{- range .Tools }}
-{"type": "function", "function": {{ .Function }}}
-{{- end }}
-</tools>
-
-For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
-<tool_call>
-{"name": <function-name>, "arguments": <args-json-object>}
-</tool_call>
-{{- end }}<|im_end|>
-{{ end }}
-{{- range \$i, \$_ := .Messages }}
-{{- \$last := eq (len (slice \$.Messages \$i)) 1 -}}
-{{- if eq .Role "user" }}<|im_start|>user
-{{ .Content }}<|im_end|>
-{{ else if eq .Role "assistant" }}<|im_start|>assistant
-{{ if .Content }}{{ .Content }}
-{{- else if .ToolCalls }}<tool_call>
-{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}
-{{ end }}</tool_call>
-{{- end }}{{ if not \$last }}<|im_end|>
-{{ end }}
-{{- else if eq .Role "tool" }}<|im_start|>user
-<tool_response>
-{{ .Content }}
-</tool_response><|im_end|>
-{{ end }}
-{{- if and (ne .Role "assistant") \$last }}<|im_start|>assistant
-{{ end }}
-{{- end }}"""
-
-PARAMETER temperature 0.2
-PARAMETER num_ctx $CTX
-PARAMETER stop "<|im_start|>"
-PARAMETER stop "<|im_end|>"
-EOF
-    TAG="$MODEL_NAME:$q"
-    echo "  Creating $TAG ..."
-    ollama create "$TAG" -f "$MF"
-done
-
-# Alias the q8_0 build as "latest" so plain `ollama pull jerboa-qwen` gets the
-# highest-fidelity tag we publish.
-if echo " $QUANTS " | grep -q ' q8_0 '; then
-    echo "  Tagging $MODEL_NAME:latest -> $MODEL_NAME:q8_0"
-    ollama cp "$MODEL_NAME:q8_0" "$MODEL_NAME:latest"
-fi
-
-# ── Step 5: Cleanup F16 intermediate ────────────────────────────────────────
-echo ""
-if [ "$KEEP_F16" = "1" ]; then
-    echo "=== Keeping F16 intermediate (KEEP_F16=1): $F16_GGUF ==="
-else
-    echo "=== Step 5: Removing F16 intermediate to reclaim ~60GB ==="
-    rm -f "$F16_GGUF"
-fi
-
-echo ""
-echo "=== Done ==="
-ollama list | grep -E "^$MODEL_NAME"
-echo ""
-echo "Push to ollama.com:"
-echo "  ./push_ollama.sh jaimef           # pushes 'latest' (= q8_0)"
-echo "  ollama push jaimef/jerboa-qwen:q4_k_m   # smaller variant"
+echo "build_ollama_gguf.sh is retired until conversion dependencies and model artifacts have a reviewed lock." >&2
+exit 1
diff --git a/configure_opencode.py b/configure_opencode.py
new file mode 100755
index 0000000..9a93b92
--- /dev/null
+++ b/configure_opencode.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+"""Atomically configure OpenCode without printing provider credentials."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import re
+import stat
+import tempfile
+from pathlib import Path
+from typing import Any
+
+
+ENDPOINT = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]{2,127}\Z")
+
+
+def fail(message: str) -> None:
+    raise SystemExit(f"configure-opencode: {message}")
+
+
+def secure_directory(path: Path) -> None:
+    path.mkdir(mode=0o700, parents=True, exist_ok=True)
+    metadata = os.stat(path, follow_symlinks=False)
+    if path.is_symlink() or not stat.S_ISDIR(metadata.st_mode):
+        fail(f"configuration directory is not a real directory: {path}")
+    if metadata.st_uid != os.getuid():
+        fail(f"configuration directory is not owned by this user: {path}")
+    os.chmod(path, 0o700)
+
+
+def load_existing(path: Path) -> dict[str, Any]:
+    if not path.exists():
+        return {}
+    metadata = os.stat(path, follow_symlinks=False)
+    if path.is_symlink() or not stat.S_ISREG(metadata.st_mode):
+        fail(f"refusing non-regular configuration file: {path}")
+    if metadata.st_uid != os.getuid():
+        fail(f"configuration file is not owned by this user: {path}")
+    if metadata.st_size > 4 * 1024 * 1024:
+        fail("existing configuration exceeds 4 MiB")
+    try:
+        data = json.loads(path.read_text())
+    except (OSError, json.JSONDecodeError) as exc:
+        fail(f"cannot parse existing configuration: {exc}")
+    if not isinstance(data, dict):
+        fail("existing configuration must be a JSON object")
+    return data
+
+
+def atomic_private_json(path: Path, value: dict[str, Any]) -> None:
+    secure_directory(path.parent)
+    payload = (json.dumps(value, indent=2, sort_keys=True) + "\n").encode()
+    fd, temporary = tempfile.mkstemp(prefix=".opencode-", suffix=".tmp", dir=path.parent)
+    temp_path = Path(temporary)
+    try:
+        os.fchmod(fd, 0o600)
+        with os.fdopen(fd, "wb", closefd=True) as handle:
+            handle.write(payload)
+            handle.flush()
+            os.fsync(handle.fileno())
+        metadata = os.stat(temp_path, follow_symlinks=False)
+        if temp_path.is_symlink() or metadata.st_uid != os.getuid() or stat.S_IMODE(metadata.st_mode) != 0o600:
+            fail("temporary configuration ownership or mode check failed")
+        if path.exists() and path.is_symlink():
+            fail("refusing to replace a configuration symlink")
+        os.replace(temp_path, path)
+        os.chmod(path, 0o600)
+        directory_fd = os.open(path.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
+        try:
+            os.fsync(directory_fd)
+        finally:
+            os.close(directory_fd)
+    finally:
+        try:
+            temp_path.unlink()
+        except FileNotFoundError:
+            pass
+
+
+def local_provider(kind: str, port: int) -> dict[str, Any]:
+    if kind == "ollama":
+        return {
+            "ollama": {
+                "npm": "@ai-sdk/openai-compatible",
+                "name": "Ollama (local)",
+                "options": {"baseURL": "http://127.0.0.1:11434/v1"},
+                "models": {"jerboa-qwen": {"name": "Jerboa Qwen"}},
+            }
+        }
+    return {
+        "mlx": {
+            "npm": "@ai-sdk/openai-compatible",
+            "name": "MLX (local)",
+            "options": {"baseURL": f"http://127.0.0.1:{port}/v1"},
+            "models": {"jerboa-local": {"name": "Jerboa local MLX"}},
+        }
+    }
+
+
+def runpod_provider(endpoint: str, key: str) -> dict[str, Any]:
+    if not ENDPOINT.fullmatch(endpoint):
+        fail("endpoint id contains invalid characters")
+    if not (20 <= len(key) <= 8192) or any(ord(ch) < 33 or ord(ch) == 127 for ch in key):
+        fail("RUNPOD_API_KEY has an invalid length or characters")
+    return {
+        "runpod": {
+            "npm": "@ai-sdk/openai-compatible",
+            "name": "RunPod (authenticated serverless)",
+            "options": {
+                "baseURL": f"https://api.runpod.ai/v2/{endpoint}/openai/v1",
+                "apiKey": key,
+            },
+            "models": {"jerboa-qwen": {"name": "Jerboa Qwen"}},
+        }
+    }
+
+
+def build_parser() -> argparse.ArgumentParser:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("mode", choices=("ollama", "mlx", "runpod", "both"))
+    parser.add_argument("value", nargs="?")
+    parser.add_argument("--config", type=Path)
+    return parser
+
+
+def main() -> None:
+    args = build_parser().parse_args()
+    config_path = args.config or Path.home() / ".config" / "opencode" / "opencode.json"
+    existing = load_existing(config_path)
+    providers: dict[str, Any]
+    if args.mode == "ollama":
+        providers = local_provider("ollama", 11434)
+    elif args.mode == "mlx":
+        try:
+            port = int(args.value or "8080")
+        except ValueError:
+            fail("MLX port must be numeric")
+        if not 1024 <= port <= 65535:
+            fail("MLX port must be between 1024 and 65535")
+        providers = local_provider("mlx", port)
+    else:
+        if not args.value:
+            fail(f"{args.mode} requires an endpoint id")
+        key = os.environ.get("RUNPOD_API_KEY", "")
+        if not key:
+            fail("RUNPOD_API_KEY is required")
+        providers = runpod_provider(args.value, key)
+        if args.mode == "both":
+            providers = {**local_provider("ollama", 11434), **providers}
+    config: dict[str, Any] = {
+        "$schema": "https://opencode.ai/config.json",
+        "provider": providers,
+    }
+    if isinstance(existing.get("mcp"), dict):
+        config["mcp"] = existing["mcp"]
+    atomic_private_json(config_path, config)
+    print(f"Wrote private OpenCode configuration to {config_path} (credentials redacted).")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/configure_opencode.sh b/configure_opencode.sh
index 6a96344..6813298 100755
--- a/configure_opencode.sh
+++ b/configure_opencode.sh
@@ -1,286 +1,5 @@
-#!/bin/bash
-# Configure OpenCode to use the Jerboa Qwen model.
-#
-# Commands:
-#   ollama                 Configure for local Ollama
-#   mlx [PORT]             Configure for local mlx_lm server (default port 8080)
-#   runpod <ENDPOINT_ID>   Configure for RunPod endpoint
-#   both <ENDPOINT_ID>     Configure both Ollama and RunPod
-#
-# Writes to ~/.config/opencode/opencode.json, preserving existing MCP config.
-#
-# Requires: RUNPOD_API_KEY env var (for runpod/both modes)
-#
-# Usage:
-#   ./configure_opencode.sh ollama
-#   ./configure_opencode.sh mlx
-#   ./configure_opencode.sh mlx 9090
-#   ./configure_opencode.sh runpod abc123
-#   ./configure_opencode.sh both abc123
+#!/bin/sh
+set -eu
 
-set -euo pipefail
-
-CONFIG_DIR="$HOME/.config/opencode"
-CONFIG_FILE="$CONFIG_DIR/opencode.json"
-
-cmd_help() {
-    echo "Usage: $0 <mode> [args]"
-    echo ""
-    echo "Modes:"
-    echo "  ollama                 Configure for local Ollama"
-    echo "  mlx [PORT]             Configure for local mlx_lm server (default 8080)"
-    echo "  runpod <ENDPOINT_ID>   Configure for RunPod serverless"
-    echo "  both <ENDPOINT_ID>     Configure both providers"
-    echo ""
-    echo "Writes to: $CONFIG_FILE"
-}
-
-write_config() {
-    local providers_json="$1"
-    local existing_mcp="{}"
-
-    if [ -f "$CONFIG_FILE" ]; then
-        existing_mcp=$(python3 -c "
-import json, sys
-try:
-    with open('$CONFIG_FILE') as f:
-        data = json.load(f)
-    mcp = data.get('mcp', {})
-    if mcp:
-        print(json.dumps(mcp))
-    else:
-        print('{}')
-except:
-    print('{}')
-" 2>/dev/null || echo "{}")
-    fi
-
-    mkdir -p "$CONFIG_DIR"
-
-    python3 -c "
-import json
-
-providers = json.loads('''$providers_json''')
-mcp = json.loads('''$existing_mcp''')
-
-config = {
-    '\$schema': 'https://opencode.ai/config.json',
-    'provider': providers
-}
-
-if mcp and mcp != {}:
-    config['mcp'] = mcp
-
-with open('$CONFIG_FILE', 'w') as f:
-    json.dump(config, f, indent=2)
-    f.write('\n')
-
-print(json.dumps(config, indent=2))
-"
-}
-
-ollama_provider() {
-    cat <<'EOF'
-{
-    "ollama": {
-        "npm": "@ai-sdk/openai-compatible",
-        "name": "Ollama (local)",
-        "options": {
-            "baseURL": "http://localhost:11434/v1"
-        },
-        "models": {
-            "jerboa-qwen": {
-                "name": "Jerboa Qwen"
-            }
-        }
-    }
-}
-EOF
-}
-
-mlx_provider() {
-    local port="$1"
-    local model_path="$(cd "$(dirname "$0")" && pwd)/jerboa-mlx-4bit-v2"
-    cat <<EOF
-{
-    "mlx": {
-        "npm": "@ai-sdk/openai-compatible",
-        "name": "MLX (local)",
-        "options": {
-            "baseURL": "http://127.0.0.1:${port}/v1"
-        },
-        "models": {
-            "${model_path}": {
-                "name": "Jerboa Qwen3 30B (MLX 4-bit)"
-            }
-        }
-    }
-}
-EOF
-}
-
-runpod_provider() {
-    local endpoint_id="$1"
-    local api_key="$2"
-    cat <<EOF
-{
-    "runpod": {
-        "npm": "@ai-sdk/openai-compatible",
-        "name": "RunPod (serverless)",
-        "options": {
-            "baseURL": "https://api.runpod.ai/v2/${endpoint_id}/openai/v1",
-            "apiKey": "${api_key}"
-        },
-        "models": {
-            "jerboa-qwen": {
-                "name": "Jerboa Qwen 7B"
-            }
-        }
-    }
-}
-EOF
-}
-
-pod_provider() {
-    # RunPod *pod* (not serverless): reach vLLM via the Cloudflare HTTP proxy.
-    # The proxy blocks non-browser User-Agents, so send one explicitly.
-    local pod_id="$1"
-    cat <<EOF
-{
-    "runpod-pod": {
-        "npm": "@ai-sdk/openai-compatible",
-        "name": "Jerboa v5 (RunPod pod)",
-        "options": {
-            "baseURL": "https://${pod_id}-8000.proxy.runpod.net/v1",
-            "headers": {
-                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36"
-            }
-        },
-        "models": {
-            "jerboa-v5": {
-                "name": "Jerboa Qwen3.6-35B-A3B v5 (BF16)"
-            }
-        }
-    }
-}
-EOF
-}
-
-both_providers() {
-    local endpoint_id="$1"
-    local api_key="$2"
-    cat <<EOF
-{
-    "ollama": {
-        "npm": "@ai-sdk/openai-compatible",
-        "name": "Ollama (local)",
-        "options": {
-            "baseURL": "http://localhost:11434/v1"
-        },
-        "models": {
-            "jerboa-qwen": {
-                "name": "Jerboa Qwen"
-            }
-        }
-    },
-    "runpod": {
-        "npm": "@ai-sdk/openai-compatible",
-        "name": "RunPod (serverless)",
-        "options": {
-            "baseURL": "https://api.runpod.ai/v2/${endpoint_id}/openai/v1",
-            "apiKey": "${api_key}"
-        },
-        "models": {
-            "jerboa-qwen": {
-                "name": "Jerboa Qwen 7B"
-            }
-        }
-    }
-}
-EOF
-}
-
-case "${1:-help}" in
-    ollama)
-        echo "=== Configuring OpenCode for local Ollama ==="
-        echo ""
-        providers=$(ollama_provider)
-        write_config "$providers"
-        echo ""
-        echo "Written to: $CONFIG_FILE"
-        echo ""
-        echo "Make sure Ollama is running: ollama serve"
-        ;;
-    mlx)
-        port="${2:-8080}"
-        echo "=== Configuring OpenCode for local MLX server (port $port) ==="
-        echo ""
-        providers=$(mlx_provider "$port")
-        write_config "$providers"
-        echo ""
-        echo "Written to: $CONFIG_FILE"
-        echo ""
-        echo "Start the MLX server in another terminal:"
-        echo "  cd $(pwd) && .venv/bin/mlx_lm server --model jerboa-mlx-4bit-v2 --port $port"
-        ;;
-    runpod)
-        endpoint_id="${2:-}"
-        if [ -z "$endpoint_id" ]; then
-            echo "Usage: $0 runpod <ENDPOINT_ID>"
-            exit 1
-        fi
-        if [ -z "${RUNPOD_API_KEY:-}" ]; then
-            echo "ERROR: RUNPOD_API_KEY not set."
-            exit 1
-        fi
-        echo "=== Configuring OpenCode for RunPod ==="
-        echo ""
-        providers=$(runpod_provider "$endpoint_id" "$RUNPOD_API_KEY")
-        write_config "$providers"
-        echo ""
-        echo "Written to: $CONFIG_FILE"
-        ;;
-    pod)
-        pod_id="${2:-}"
-        if [ -z "$pod_id" ]; then
-            echo "Usage: $0 pod <POD_ID>   (e.g. y4vysb5bfzdxiw)"
-            exit 1
-        fi
-        echo "=== Configuring OpenCode for RunPod pod $pod_id (vLLM via proxy) ==="
-        echo ""
-        providers=$(pod_provider "$pod_id")
-        write_config "$providers"
-        echo ""
-        echo "Written to: $CONFIG_FILE"
-        echo ""
-        echo "Endpoint: https://${pod_id}-8000.proxy.runpod.net/v1  (model: jerboa-v5)"
-        echo "NOTE: the model is grounded by the system prompt — ensure the agent"
-        echo "      sends a Jerboa system message, or answers may drift."
-        ;;
-    both)
-        endpoint_id="${2:-}"
-        if [ -z "$endpoint_id" ]; then
-            echo "Usage: $0 both <ENDPOINT_ID>"
-            exit 1
-        fi
-        if [ -z "${RUNPOD_API_KEY:-}" ]; then
-            echo "ERROR: RUNPOD_API_KEY not set."
-            exit 1
-        fi
-        echo "=== Configuring OpenCode for Ollama + RunPod ==="
-        echo ""
-        providers=$(both_providers "$endpoint_id" "$RUNPOD_API_KEY")
-        write_config "$providers"
-        echo ""
-        echo "Written to: $CONFIG_FILE"
-        ;;
-    help|--help|-h)
-        cmd_help
-        ;;
-    *)
-        echo "Unknown mode: $1"
-        echo ""
-        cmd_help
-        exit 1
-        ;;
-esac
+SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+exec python3 "$SCRIPT_DIR/configure_opencode.py" "$@"
diff --git a/convert_to_mlx.sh b/convert_to_mlx.sh
index cfffc72..820c041 100755
--- a/convert_to_mlx.sh
+++ b/convert_to_mlx.sh
@@ -14,8 +14,8 @@ if [ ! -d "$SRC" ]; then
 fi
 
 if ! python3 -c "import mlx_lm" 2>/dev/null; then
-  echo "Installing mlx-lm..."
-  pip install --quiet mlx-lm
+  echo "error: mlx_lm is not present in the reviewed environment; runtime installs are forbidden." >&2
+  exit 1
 fi
 
 echo "Converting $SRC -> $DST (4-bit MLX)..."
diff --git a/deploy_runpod.sh b/deploy_runpod.sh
index e6c849f..b01d425 100755
--- a/deploy_runpod.sh
+++ b/deploy_runpod.sh
@@ -1,274 +1,5 @@
-#!/bin/bash
-# Deploy the fine-tuned Jerboa Qwen model to RunPod Serverless via HuggingFace.
-#
-# This script:
-#   1. Downloads the pre-merged model from Together AI (~14GB, merged server-side)
-#   2. Creates a HuggingFace repo and uploads the model
-#   3. Creates a RunPod serverless vLLM endpoint via GraphQL API
-#
-# Prerequisites:
-#   pip install together huggingface_hub
-#   export TOGETHER_API_KEY="your-key"
-#   export RUNPOD_API_KEY="your-key"    # from https://www.runpod.io/console/user/settings
-#   hf auth login                        # needs write token from https://huggingface.co/settings/tokens
-#
-# Usage:
-#   ./deploy_runpod.sh [HF_REPO]
-#
-# Example:
-#   ./deploy_runpod.sh jaimef21/jerboa-qwen-7b
-#
-# JOB_ID is auto-detected from .together_state.json (set by train_together.py).
-# Override with: JOB_ID=ft-xxx ./deploy_runpod.sh ...
+#!/bin/sh
+set -eu
 
-set -euo pipefail
-cd "$(dirname "$0")"
-
-HF_REPO="${1:-jaimef21/jerboa-qwen-7b}"
-MERGED_DIR="./together-merged"
-
-# Auto-detect JOB_ID from state file unless overridden
-if [ -z "${JOB_ID:-}" ]; then
-    if [ -f ".together_state.json" ]; then
-        JOB_ID=$(python3 -c "import json; print(json.load(open('.together_state.json')).get('job_id',''))")
-    fi
-fi
-
-if [ -z "${JOB_ID:-}" ]; then
-    echo "ERROR: No JOB_ID found."
-    echo "Either set it manually:    JOB_ID=ft-xxx ./deploy_runpod.sh ..."
-    echo "Or train first:            python3 train_together.py train"
-    exit 1
-fi
-
-echo "=== Deploying jerboa-qwen to RunPod via HuggingFace ==="
-echo "HuggingFace repo: $HF_REPO"
-echo "Together AI job:  $JOB_ID"
-echo ""
-
-# ── Check prerequisites ──────────────────────────────────────────────
-if ! command -v together &>/dev/null; then
-    echo "ERROR: 'together' CLI not found. Run: pip install together"
-    exit 1
-fi
-
-if [ -z "${TOGETHER_API_KEY:-}" ]; then
-    echo "ERROR: TOGETHER_API_KEY not set. Run: export TOGETHER_API_KEY=your-key"
-    exit 1
-fi
-
-if [ -z "${RUNPOD_API_KEY:-}" ]; then
-    echo "ERROR: RUNPOD_API_KEY not set."
-    echo "Get your key from: https://www.runpod.io/console/user/settings"
-    echo "Then run: export RUNPOD_API_KEY=your-key"
-    exit 1
-fi
-
-python3 -c "from huggingface_hub import HfApi; HfApi().whoami()" 2>/dev/null || {
-    echo "ERROR: Not logged into HuggingFace. Run: hf auth login"
-    exit 1
-}
-
-# ── Step 1: Download pre-merged model from Together AI ───────────────
-echo "=== Step 1: Download pre-merged model from Together AI ==="
-if [ -d "$MERGED_DIR" ] && [ -f "$MERGED_DIR/config.json" ]; then
-    echo "Merged model already downloaded at $MERGED_DIR"
-else
-    echo "Downloading merged model for job $JOB_ID (~14GB) ..."
-    echo "Together AI merges the LoRA adapter with the base model server-side."
-    echo "No local GPU or 32GB RAM needed."
-    mkdir -p "$MERGED_DIR"
-    together fine-tuning download "$JOB_ID" \
-        --checkpoint-type merged \
-        --output_dir "$MERGED_DIR"
-
-    # Together AI downloads a zstd-compressed tar — extract it
-    if [ ! -f "$MERGED_DIR/config.json" ]; then
-        echo "Extracting compressed archive ..."
-        cd "$MERGED_DIR"
-        for f in *; do
-            if file "$f" | grep -q "Zstandard"; then
-                tar --zstd -xf "$f" && rm "$f"
-                break
-            fi
-        done
-        cd ..
-    fi
-
-    echo "Downloaded to $MERGED_DIR"
-fi
-
-if [ ! -f "$MERGED_DIR/config.json" ]; then
-    echo "ERROR: config.json not found in $MERGED_DIR"
-    ls -la "$MERGED_DIR"/
-    exit 1
-fi
-
-echo ""
-echo "=== Step 2: Upload to HuggingFace ==="
-echo "Uploading to https://huggingface.co/$HF_REPO ..."
-
-python3 -c "
-from huggingface_hub import HfApi, create_repo
-api = HfApi()
-
-try:
-    create_repo('$HF_REPO', repo_type='model', exist_ok=True)
-    print('Repo ready: $HF_REPO')
-except Exception as e:
-    print(f'Repo creation: {e}')
-
-api.upload_folder(
-    folder_path='$MERGED_DIR',
-    repo_id='$HF_REPO',
-    commit_message='Upload Jerboa Qwen 7B - fine-tuned for Jerboa Scheme',
-)
-print('Upload complete!')
-"
-
-echo ""
-echo "Model uploaded to: https://huggingface.co/$HF_REPO"
-
-echo ""
-echo "=== Step 3: Create RunPod Serverless Endpoint ==="
-
-echo "Creating RunPod serverless template ..."
-TEMPLATE_RESULT=$(curl -s --request POST \
-    --header 'content-type: application/json' \
-    --url "https://api.runpod.io/graphql?api_key=${RUNPOD_API_KEY}" \
-    --data "{\"query\": \"mutation { saveTemplate(input: { name: \\\"jerboa-qwen-vllm\\\", imageName: \\\"runpod/worker-v1-vllm:stable-cuda12.1.0\\\", isServerless: true, containerDiskInGb: 20, dockerArgs: \\\"\\\", volumeInGb: 0, env: [ { key: \\\"MODEL_NAME\\\", value: \\\"$HF_REPO\\\" }, { key: \\\"MAX_MODEL_LEN\\\", value: \\\"8192\\\" }, { key: \\\"GPU_MEMORY_UTILIZATION\\\", value: \\\"0.90\\\" } ] }) { id name imageName } }\"}")
-
-TEMPLATE_ID=$(echo "$TEMPLATE_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['saveTemplate']['id'])" 2>/dev/null || true)
-
-if [ -z "$TEMPLATE_ID" ]; then
-    echo "ERROR: Failed to create template."
-    echo "Response: $TEMPLATE_RESULT"
-    echo ""
-    echo "You may need to create the endpoint manually via the RunPod UI."
-    echo "See instructions below."
-    MANUAL=1
-else
-    echo "Template created: $TEMPLATE_ID"
-
-    echo "Creating serverless endpoint ..."
-    ENDPOINT_RESULT=$(curl -s --request POST \
-        --header 'content-type: application/json' \
-        --url "https://api.runpod.io/graphql?api_key=${RUNPOD_API_KEY}" \
-        --data "{\"query\": \"mutation { saveEndpoint(input: { name: \\\"jerboa-qwen\\\", templateId: \\\"$TEMPLATE_ID\\\", gpuIds: \\\"AMPERE_24\\\", workersMin: 0, workersMax: 1, idleTimeout: 60, scalerType: \\\"QUEUE_DELAY\\\", scalerValue: 4 }) { id name gpuIds templateId workersMin workersMax idleTimeout } }\"}")
-
-    ENDPOINT_ID=$(echo "$ENDPOINT_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['saveEndpoint']['id'])" 2>/dev/null || true)
-
-    if [ -z "$ENDPOINT_ID" ]; then
-        echo "ERROR: Failed to create endpoint."
-        echo "Response: $ENDPOINT_RESULT"
-        MANUAL=1
-    else
-        MANUAL=0
-        echo ""
-        echo "=== Endpoint Created! ==="
-        echo ""
-        echo "  Endpoint ID:  $ENDPOINT_ID"
-        echo "  API URL:      https://api.runpod.ai/v2/$ENDPOINT_ID/openai/v1"
-        echo "  Console:      https://www.runpod.io/console/serverless/$ENDPOINT_ID"
-        echo "  GPU:          AMPERE_24 (~\$0.39/hr)"
-        echo "  Min Workers:  0 (scale to zero)"
-        echo "  Max Workers:  1"
-        echo "  Idle Timeout: 60s"
-    fi
-fi
-
-if [ "${MANUAL:-0}" = "1" ]; then
-    echo ""
-    echo "=== Manual RunPod Setup ==="