Files
boshu2__agentops/tests/python/test_gc33_thin_pack.py
Bo 189bc81867 feat(gc): 3.3 flow is the mayor-driven door — human or agent orchestrated (#985)
## What

The 3.3 GC pack flow becomes Gas City's native human door, drivable by
humans and agents alike (operator decision). Five commits:

1. **a903c23f8** `feat(gc)`: Mayor prompt rewritten observe-only →
**shepherd** — polls external rigs' ready step beads and dispatches each
via `gc sling <gc.run_target> <bead> --nudge`. Never claims, never
authors, never closes.
2. **e58aeb5cb** `feat(gc)`: `invoke.sh mayor start|status|tell` — the
agent control surface over native primitives (`gc session wake`, `gc
session list --json`, `gc mail send --notify`; mail chosen over
sling-text to avoid v1.3.5's inline-text auto-create). Mayor is now a
`mode=always` standing session.
3. **bd8c34df6** `docs(gc)`: README — two doors (human attaches, agent
drives via mail), heartbeat propulsion, #4586 context.
4. **2b6ffe9cf** `feat(gc)`: `skills/using-gc` rewritten as the
mayor-orchestration skill (drive loop, bead-ids-only doctrine, GC
liveness truth stack with both codex wedge classes, stall protocol) +
all regenerated projections.
5. **362f4af79** `fix(gc)` (cross-family review: 1 BLOCKER + 5 MAJORs,
all fixed): **shepherd-heartbeat order** (native `gc order`, 3m cooldown
— mode=always keeps the Mayor resident but nothing re-prompts it; the
order is the liveness engine); HQ filtered from rig enumeration; stall
recovery = `session wake` (re-sling documented as a no-op for routed
beads); `mayor status` tri-state fail-closed; mail authority limited to
dispatch/status with bodies treated as untrusted data (pause/resume
dropped — stateless under fresh wake); test stubs reject non-allowlisted
verbs.

## Why

On v1.3.5, demand-driven worker spawning never fires for rig-routed work
(upstream gastownhall/gascity#4586, ours, with a stock-pack no-LLM
repro). Shepherd-dispatch by a standing Mayor is both the
v1.3.5-functional propulsion path AND the stock-GC operating pattern
(their mayor prompt: create → sling bead-id → monitor) — so this flow
survives the upstream fix rather than fighting it. Intent stays
caller-owned: drivers hand the Mayor bead IDs, never prose work.

## Verification

- `tests.python.test_gc33_thin_pack`: 34 passed (2 integration-gated
skips)
- `shellcheck -S warning` + `bash -n` clean; `scripts/regen-all.sh
--check`: all 11 projections current
- Every gc primitive verified against v1.3.5 source (cmd_mail.go,
cmd_session_wake.go, cmd_sling.go, cmd_rig.go, decode_sessions.go,
orderdiscovery)
- Codex adversarial review round: all findings fixed
- **Not yet proven live**: heartbeat-order discovery/scheduling,
mode=always residency, wake-based recovery, end-to-end propulsion — the
live canary follows this merge.
2026-07-23 17:08:28 +00:00

1157 lines
59 KiB
Python

from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import platform
import stat
import subprocess
import tarfile
import tempfile
import textwrap
import time
import tomllib
import unittest
REPO_ROOT = Path(__file__).resolve().parents[2]
DEPLOY = REPO_ROOT / "deploy" / "gc"
FACTORY = REPO_ROOT / "packs" / "agentops-factory"
EXECUTOR = REPO_ROOT / "packs" / "agentops-executor"
GC_COMMIT = "8ffc009ded781a2ada2077f3a29bd712b2def0bf"
BD_COMMIT = "8e4e59d39f3459a43cf21a3236a13eca4dd874f7"
PINNED_INTEGRATION = bool(os.environ.get("GC_BIN") and os.environ.get("AGENTOPS_GC_INTEGRATION") == "1")
def run(*argv: str, cwd: Path | None = None, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
argv,
cwd=cwd,
env=env,
check=False,
capture_output=True,
text=True,
timeout=120,
)
def git(*argv: str, cwd: Path) -> subprocess.CompletedProcess[str]:
return run("git", *argv, cwd=cwd)
class ThinPackTests(unittest.TestCase):
def make_repository(self, root: Path) -> tuple[Path, Path]:
remote = root / "remote.git"
repo = root / "repo"
self.assertEqual(run("git", "init", "--bare", str(remote)).returncode, 0)
self.assertEqual(run("git", "init", "-b", "main", str(repo)).returncode, 0)
self.assertEqual(git("config", "user.name", "AgentOps Test", cwd=repo).returncode, 0)
self.assertEqual(git("config", "user.email", "agentops-test@example.invalid", cwd=repo).returncode, 0)
(repo / "README.md").write_text("seed\n", encoding="utf-8")
(repo / ".gitignore").write_text(".beads/*\n!.beads/identity.toml\n.agents/\n.claude/\n", encoding="utf-8")
self.assertEqual(git("add", "README.md", ".gitignore", cwd=repo).returncode, 0)
self.assertEqual(git("commit", "-m", "seed", cwd=repo).returncode, 0)
self.assertEqual(git("remote", "add", "origin", str(remote), cwd=repo).returncode, 0)
self.assertEqual(git("push", "-u", "origin", "main", cwd=repo).returncode, 0)
self.assertEqual(run("git", "-C", str(remote), "symbolic-ref", "HEAD", "refs/heads/main").returncode, 0)
return repo, remote
def initialize_beads(self, repo: Path, gc_bin: Path) -> str:
bd_bin = gc_bin.parent / "bd"
initialized = run(
str(bd_bin), "init", "--non-interactive", "--skip-agents", "--skip-hooks",
"--setup-exclude", "--prefix", "test",
cwd=repo,
)
self.assertEqual(initialized.returncode, 0, initialized.stderr)
# GC v1.3.5 adopts the on-disk prefix, while BD v1.1.0 stores the init
# prefix in Dolt only. Model the GC-compatible source config that an
# existing rig carries before official bd bootstrap moves its durable
# database into a fresh city's managed server.
config_path = repo / ".beads/config.yaml"
config_path.write_text(
"issue_prefix: test\nissue-prefix: test\n" + config_path.read_text(encoding="utf-8"),
encoding="utf-8",
)
created = run(str(bd_bin), "create", "--title", "source bead", "--type", "task", "--json", cwd=repo)
self.assertEqual(created.returncode, 0, created.stderr)
source_bead = json.loads(created.stdout)["id"]
self.assertTrue(source_bead.startswith("test-"))
committed = run(str(bd_bin), "dolt", "commit", "-m", "source bead", cwd=repo)
self.assertEqual(committed.returncode, 0, committed.stderr)
pushed = run(str(bd_bin), "dolt", "push", cwd=repo)
self.assertEqual(pushed.returncode, 0, pushed.stderr)
self.assertEqual(git("add", ".gitignore", cwd=repo).returncode, 0)
if (repo / ".beads/identity.toml").exists():
self.assertEqual(git("add", "-f", ".beads/identity.toml", cwd=repo).returncode, 0)
if git("diff", "--cached", "--quiet", cwd=repo).returncode != 0:
self.assertEqual(git("commit", "-m", "adopt official beads identity", cwd=repo).returncode, 0)
self.assertEqual(git("push", "origin", "main", cwd=repo).returncode, 0)
return source_bead
def test_runtime_surface_is_native_and_single_owner(self) -> None:
removed = (
REPO_ROOT / "cli/cmd/agentops-gc-delivery",
REPO_ROOT / "cli/internal/gcadapter/delivery",
FACTORY / "assets/schemas",
FACTORY / "assets/scripts/factory_feeder.py",
FACTORY / "assets/scripts/program_start.py",
FACTORY / "assets/scripts/role_adapter.py",
EXECUTOR / "assets/schemas/gc-execution-envelope.v1.schema.json",
EXECUTOR / "commands/run-packet",
DEPLOY / "reliability.py",
DEPLOY / "known-errors.json",
DEPLOY / "fork-baseline.json",
DEPLOY / "claude-interactive.sh",
)
self.assertEqual(
[str(path) for path in removed if path.is_file() or (path.is_dir() and any(item.is_file() for item in path.rglob("*")))],
[],
)
invoke = (DEPLOY / "invoke.sh").read_text(encoding="utf-8")
# Official single-bead intake: feed slings to the rig-scoped planner with
# the native formula attached; it never slings to the city-scoped Mayor.
self.assertIn('sling "$rig_name/agentops.plan-reviewer" "$1"', invoke)
self.assertIn("--on agentops-experiment", invoke)
self.assertNotIn("sling agentops.mayor", invoke)
for obsolete in ("program start", "factory_feeder", "role_adapter", "run-packet"):
self.assertNotIn(obsolete, invoke)
city_text = (DEPLOY / "city.toml").read_text(encoding="utf-8").replace("__GC_MAX_ACTIVE_SESSIONS__", "2")
city = tomllib.loads(city_text)
self.assertEqual(city["providers"]["claude"]["print_args"], [])
self.assertNotIn("path", city["providers"]["claude"])
self.assertNotIn("args_append", city["providers"]["claude"])
self.assertEqual(city["daemon"]["formula_v2"], True)
def test_formula_is_one_native_dependency_chain(self) -> None:
formula = tomllib.loads((FACTORY / "formulas/agentops-experiment.toml").read_text(encoding="utf-8"))
steps = formula["steps"]
self.assertEqual([step["id"] for step in steps], ["plan", "implement", "validate", "deliver"])
self.assertNotIn("needs", steps[0])
self.assertEqual([step["needs"] for step in steps[1:]], [["plan"], ["implement"], ["validate"]])
self.assertEqual(
[step["metadata"]["gc.run_target"] for step in steps],
["{{plan_target}}", "{{implement_target}}", "{{validate_target}}", "{{refiner_target}}"],
)
self.assertEqual(set(formula["vars"]), {"work_dir", "plan_target", "implement_target", "validate_target", "refiner_target"})
def test_role_models_match_factory_policy(self) -> None:
expected = {
FACTORY / "agents/mayor/agent.toml": ("claude", "fable-5", "adaptive"),
FACTORY / "agents/plan-reviewer/agent.toml": ("codex", "gpt-5.6-sol", "high"),
FACTORY / "agents/refiner/agent.toml": ("claude", "fable-5", "adaptive"),
EXECUTOR / "agents/implementer/agent.toml": ("codex", "gpt-5.6-terra", "high"),
EXECUTOR / "agents/implementer-claude/agent.toml": ("claude", "opus-4.8", "medium"),
EXECUTOR / "agents/validator/agent.toml": ("codex", "gpt-5.6-sol", "high"),
}
mayor_agent = FACTORY / "agents/mayor/agent.toml"
for path, (provider, model, effort) in expected.items():
config = tomllib.loads(path.read_text(encoding="utf-8"))
self.assertEqual(config["provider"], provider, path)
self.assertEqual(config["option_defaults"], {"permission_mode": config["option_defaults"]["permission_mode"], "model": model, "effort": effort}, path)
# Every role is a one_shot worker except the Mayor, which is the
# standing dispatch shepherd (asserted separately).
if path == mayor_agent:
self.assertNotIn("lifecycle", config, path)
else:
self.assertEqual(config["lifecycle"], "one_shot", path)
runtime_text = "\n".join(path.read_text(encoding="utf-8") for path in expected)
self.assertNotIn("luna", runtime_text.lower())
def test_exact_official_toolchain_provenance(self) -> None:
lock = json.loads((DEPLOY / "toolchain.lock.json").read_text(encoding="utf-8"))
self.assertEqual(lock["schema_version"], 1)
self.assertEqual(len(lock["accepted_pairs"]), 1)
pair = lock["accepted_pairs"][0]
self.assertEqual(pair["status"], "qualified")
self.assertEqual(pair["gc"]["repository"], "https://github.com/gastownhall/gascity.git")
self.assertEqual(pair["gc"]["source_commit"], GC_COMMIT)
self.assertEqual(pair["bd"]["repository"], "https://github.com/steveyegge/beads.git")
self.assertEqual(pair["bd"]["source_commit"], BD_COMMIT)
bootstrap = (DEPLOY / "bootstrap.sh").read_text(encoding="utf-8")
self.assertIn(GC_COMMIT, bootstrap)
self.assertIn(BD_COMMIT, bootstrap)
self.assertIn('--prefix "$bead_prefix" --start-suspended --adopt', bootstrap)
self.assertNotIn('"$bd_bin" init', bootstrap)
# --- Offline materialize-toolchain fixtures -------------------------------
#
# materialize-toolchain.sh downloads official prebuilt release archives and
# verifies them against the pinned official checksums. These fixtures drive
# the identical verification chain fully offline: AGENTOPS_GC_ARCHIVE_CACHE
# supplies pre-placed assets instead of the network, and --lock supplies a
# fixture lock that pins the fabricated fixture shas. No test hits GitHub.
@staticmethod
def platform_tag() -> str | None:
system = {"Darwin": "darwin", "Linux": "linux"}.get(platform.system())
machine = {"arm64": "arm64", "aarch64": "arm64", "x86_64": "amd64", "amd64": "amd64"}.get(platform.machine())
if not system or not machine:
return None
return f"{system}_{machine}"
def build_offline_fixture(
self,
root: Path,
*,
gc_reported_version: str = "1.3.5",
gc_reported_commit: str = "8ffc009ded781a2ada2077f3a29bd712b2def0bf",
bd_reported_version: str = "1.1.0",
bd_reported_commit: str = "8e4e59d39",
) -> dict[str, object]:
# The lock always pins the correct locked identity; only what the stub
# binaries *report* varies. The checksum chain stays internally valid for
# any stub content (checksums file and lock are derived from the actual
# archive digests), so a reported-identity mismatch isolates the final
# version/commit binding step from the earlier hash links.
tag = self.platform_tag()
if tag is None:
self.skipTest(f"no release-archive mapping for {platform.system()}/{platform.machine()}")
cache = root / "cache"
cache.mkdir()
gc_script = (
'#!/bin/sh\n'
'if [ "$1" = "version" ] && [ "$2" = "--json" ]; then\n'
' printf \'{"version":"' + gc_reported_version + '","commit":"'
+ gc_reported_commit + '-dirty","ok":true}\\n\'\n'
' exit 0\n'
'fi\n'
'exit 1\n'
)
bd_script = (
'#!/bin/sh\n'
'if [ "$1" = "version" ]; then\n'
' printf \'bd version ' + bd_reported_version + ' ('
+ bd_reported_commit + ': fixture)\\n\'\n'
' exit 0\n'
'fi\n'
'exit 1\n'
)
def make_archive(archive_name: str, binname: str, script: str) -> Path:
src = root / (binname + "_src")
src.mkdir(exist_ok=True)
binpath = src / binname
binpath.write_text(script, encoding="utf-8")
binpath.chmod(0o755)
archive_path = cache / archive_name
with tarfile.open(archive_path, "w:gz") as tf:
tf.add(binpath, arcname=binname)
return archive_path
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
gc_archive_name = f"gascity_1.3.5_{tag}.tar.gz"
bd_archive_name = f"beads_1.1.0_{tag}.tar.gz"
gc_archive = make_archive(gc_archive_name, "gc", gc_script)
bd_archive = make_archive(bd_archive_name, "bd", bd_script)
gc_ck = cache / "gascity_1.3.5_checksums.txt"
gc_ck.write_text(f"{sha256(gc_archive)} {gc_archive_name}\n", encoding="utf-8")
bd_ck = cache / "checksums.txt"
bd_ck.write_text(f"{sha256(bd_archive)} {bd_archive_name}\n", encoding="utf-8")
lock = {
"schema_version": 1,
"accepted_pairs": [
{
"id": "gascity-v1.3.5-beads-v1.1.0",
"status": "qualified",
"gc": {
"repository": "https://github.com/gastownhall/gascity.git",
"ref": "v1.3.5",
"version": "1.3.5",
"source_commit": GC_COMMIT,
"runtime_commit": "8ffc009d",
"release_checksum_asset": "gascity_1.3.5_checksums.txt",
"release_checksum_sha256": sha256(gc_ck),
},
"bd": {
"repository": "https://github.com/steveyegge/beads.git",
"ref": "v1.1.0",
"version": "1.1.0",
"source_commit": BD_COMMIT,
"runtime_commit": "8e4e59d39",
"release_checksum_asset": "checksums.txt",
"release_checksum_sha256": sha256(bd_ck),
},
}
],
}
lock_path = root / "toolchain.lock.json"
lock_path.write_text(json.dumps(lock, indent=2), encoding="utf-8")
return {
"cache": cache,
"lock": lock_path,
"gc_ck": gc_ck,
"bd_ck": bd_ck,
"gc_archive_name": gc_archive_name,
"bd_archive_name": bd_archive_name,
"tag": tag,
}
def run_materialize(self, output: Path, lock: Path, cache: Path, extra_env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["AGENTOPS_GC_ARCHIVE_CACHE"] = str(cache)
if extra_env:
env.update(extra_env)
return run(
str(DEPLOY / "materialize-toolchain.sh"), "--output", str(output), "--lock", str(lock),
env=env,
)
def test_materialize_offline_happy_path_installs_verified_pair(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
fixture = self.build_offline_fixture(root)
output = root / "toolchain"
result = self.run_materialize(output, fixture["lock"], fixture["cache"])
self.assertEqual(result.returncode, 0, result.stderr)
gc_bin = output / "bin/gc"
bd_bin = output / "bin/bd"
self.assertTrue(gc_bin.is_file())
self.assertTrue(bd_bin.is_file())
self.assertTrue(gc_bin.stat().st_mode & stat.S_IXUSR)
self.assertTrue(bd_bin.stat().st_mode & stat.S_IXUSR)
receipt = json.loads((output / "toolchain.json").read_text(encoding="utf-8"))
self.assertEqual(receipt["schema_version"], 2)
self.assertEqual(set(receipt["runtime"]), {"gc", "bd"})
self.assertEqual(receipt["runtime"]["gc"]["path"], "bin/gc")
self.assertEqual(receipt["runtime"]["bd"]["path"], "bin/bd")
self.assertEqual(receipt["runtime"]["gc"]["version"], "1.3.5")
self.assertEqual(receipt["runtime"]["bd"]["version"], "1.1.0")
self.assertEqual(receipt["runtime"]["gc"]["commit"], GC_COMMIT)
self.assertEqual(receipt["runtime"]["bd"]["commit"], "8e4e59d39")
self.assertEqual(receipt["runtime"]["gc"]["sha256"], hashlib.sha256(gc_bin.read_bytes()).hexdigest())
self.assertEqual(receipt["runtime"]["bd"]["sha256"], hashlib.sha256(bd_bin.read_bytes()).hexdigest())
self.assertEqual(receipt["pair"]["status"], "qualified")
self.assertEqual(receipt["pair"]["gc"]["source_commit"], GC_COMMIT)
self.assertEqual(receipt["pair"]["bd"]["source_commit"], BD_COMMIT)
def test_materialize_offline_rejects_checksums_file_tamper(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
fixture = self.build_offline_fixture(root)
lock_path = fixture["lock"]
lock = json.loads(lock_path.read_text(encoding="utf-8"))
lock["accepted_pairs"][0]["gc"]["release_checksum_sha256"] = "0" * 64
lock_path.write_text(json.dumps(lock, indent=2), encoding="utf-8")
output = root / "toolchain"
result = self.run_materialize(output, lock_path, fixture["cache"])
self.assertNotEqual(result.returncode, 0)
self.assertIn("checksums asset", result.stderr)
self.assertIn("sha256 mismatch", result.stderr)
self.assertFalse(output.exists())
def test_materialize_offline_rejects_archive_tamper(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
fixture = self.build_offline_fixture(root)
# Point the checksums file at a wrong archive digest, then re-pin the
# lock to the tampered checksums file so its own digest still passes.
gc_ck = fixture["gc_ck"]
tampered = f"{'0' * 64} {fixture['gc_archive_name']}\n"
gc_ck.write_text(tampered, encoding="utf-8")
lock_path = fixture["lock"]
lock = json.loads(lock_path.read_text(encoding="utf-8"))
lock["accepted_pairs"][0]["gc"]["release_checksum_sha256"] = hashlib.sha256(tampered.encode()).hexdigest()
lock_path.write_text(json.dumps(lock, indent=2), encoding="utf-8")
output = root / "toolchain"
result = self.run_materialize(output, lock_path, fixture["cache"])
self.assertNotEqual(result.returncode, 0)
self.assertIn("archive", result.stderr)
self.assertIn("sha256 mismatch", result.stderr)
self.assertFalse(output.exists())
def test_materialize_rejects_unsupported_platform(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
fixture = self.build_offline_fixture(root)
stub_dir = root / "stub"
stub_dir.mkdir()
uname = stub_dir / "uname"
uname.write_text(
'#!/bin/sh\nif [ "$1" = "-s" ]; then echo Plan9; else echo sparc; fi\n',
encoding="utf-8",
)
uname.chmod(0o755)
output = root / "toolchain"
result = self.run_materialize(
output, fixture["lock"], fixture["cache"],
extra_env={"PATH": f"{stub_dir}:{os.environ['PATH']}"},
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("unsupported operating system", result.stderr)
self.assertFalse(output.exists())
def test_materialize_offline_rejects_gc_commit_mismatch(self) -> None:
# Valid checksum chain, but the installed gc binary reports a different
# commit than the lock pins. Exercises the gc version/commit binding.
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
fixture = self.build_offline_fixture(
root, gc_reported_commit="1234567890abcdef1234567890abcdef12345678",
)
output = root / "toolchain"
result = self.run_materialize(output, fixture["lock"], fixture["cache"])
self.assertNotEqual(result.returncode, 0)
self.assertIn("installed gc commit", result.stderr)
self.assertFalse(output.exists())
def test_materialize_offline_rejects_bd_version_mismatch(self) -> None:
# Valid checksum chain, but the installed bd binary reports a different
# version than the lock pins. Exercises the bd version/commit binding.
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
fixture = self.build_offline_fixture(root, bd_reported_version="9.9.9")
output = root / "toolchain"
result = self.run_materialize(output, fixture["lock"], fixture["cache"])
self.assertNotEqual(result.returncode, 0)
self.assertIn("installed bd version", result.stderr)
self.assertIn("9.9.9", result.stderr)
self.assertFalse(output.exists())
def test_executable_helper_budget_and_shell_syntax(self) -> None:
scripts = sorted(DEPLOY.glob("*.sh"))
line_count = sum(len(path.read_text(encoding="utf-8").splitlines()) for path in scripts)
self.assertLessEqual(line_count, 2_000)
for path in scripts:
self.assertTrue(path.stat().st_mode & stat.S_IXUSR, path)
result = run("/bin/bash", "-n", str(path))
self.assertEqual(result.returncode, 0, result.stderr)
def test_lifecycle_helpers_export_the_pinned_gc_binary(self) -> None:
for name in ("bootstrap.sh", "invoke.sh", "teardown.sh"):
text = (DEPLOY / name).read_text(encoding="utf-8")
self.assertIn("export GC_BIN\n", text, name)
def test_intake_never_routes_through_the_city_scoped_mayor(self) -> None:
# Static consistency proof: intake (feed) homes rig beads on the rig
# planner and never slings the source bead to agentops.mayor. The Mayor
# is a dispatch shepherd (it slings ready STEP beads to their rig
# run-targets), so it must never be a sling *target* on the intake path.
surfaces = list(DEPLOY.glob("*.sh")) + list(FACTORY.rglob("*.md"))
for path in surfaces:
text = path.read_text(encoding="utf-8")
self.assertNotIn("sling agentops.mayor", text, path)
mayor_prompt = (FACTORY / "agents/mayor/prompt.template.md").read_text(encoding="utf-8")
# Doctrine invariant: the Mayor dispatches but never claims. It forbids
# the cross-store claim and never carries an instruction to run it.
self.assertIn("Do not run `gc hook --claim`", mayor_prompt)
self.assertNotIn('Run `"$GC_BIN" hook --claim', mayor_prompt)
@staticmethod
def _sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
@staticmethod
def _tree_sha(root: Path) -> str:
h = hashlib.sha256()
for parent, dirs, files in os.walk(root):
dirs.sort()
for name in sorted(files):
p = os.path.join(parent, name)
rel = os.path.relpath(p, root)
h.update(rel.encode() + b"\0" + Path(p).read_bytes() + b"\0")
return h.hexdigest()
# gc/bd stubs record one argv element per line so tests assert on exact argv
# arrays (argument boundaries and quoting survive), never a flattened "$*".
# Behavior is driven by env; the fixture records the stub's digest
# dynamically, so the content may change freely.
#
# Fidelity: the gc stub REJECTS (nonzero + stderr) any first verb not on the
# allowlist of verified v1.3.5 surfaces the pack actually drives through gc
# (bd goes through the separate bd stub). This makes a test fail loudly if the
# pack ever calls an unverified gc verb. GC_STUB_FAIL=<verb> forces a nonzero
# exit for an allowlisted verb, to exercise fail-closed error propagation.
_GC_STUB = (
"#!/usr/bin/env python3\n"
"import os, sys\n"
"argv = sys.argv[1:]\n"
"log = os.environ.get('GC_ARGV_LOG')\n"
"if log:\n"
" with open(log, 'a', encoding='utf-8') as f:\n"
" f.write('\\n'.join(argv) + '\\n')\n"
"allowed = {'sling', 'status', 'doctor', 'session', 'mail'}\n"
"if not argv or argv[0] not in allowed:\n"
" sys.stderr.write('gc stub: refusing non-allowlisted command: %s\\n' % (argv[0] if argv else '<none>'))\n"
" sys.exit(2)\n"
"fail = os.environ.get('GC_STUB_FAIL')\n"
"if fail and argv[0] == fail:\n"
" sys.stderr.write('gc stub: forced failure for %s\\n' % fail)\n"
" sys.exit(1)\n"
"sys.exit(0)\n"
)
_BD_STUB = (
"#!/usr/bin/env python3\n"
"import json, os, sys\n"
"argv = sys.argv[1:]\n"
"log = os.environ.get('BD_ARGV_LOG')\n"
"if log:\n"
" with open(log, 'a', encoding='utf-8') as f:\n"
" f.write('\\n'.join(argv) + '\\n---\\n')\n"
"mode = os.environ.get('BD_STUB_MODE', 'ok')\n"
"if 'create' in argv:\n"
" print(json.dumps({'id': 'testrig-9'})); sys.exit(0)\n"
"if 'show' in argv:\n"
" bead = argv[argv.index('show') + 1]\n"
" if mode == 'notfound':\n"
" sys.stderr.write('Issue %s not found\\n' % bead); sys.exit(1)\n"
" returned = 'totally-different' if mode == 'mismatch' else bead\n"
" print(json.dumps([{'id': returned}])); sys.exit(0)\n"
"sys.exit(2)\n"
)
def _invoke_env(
self,
*,
gc_log: Path | None = None,
bd_log: Path | None = None,
bd_mode: str = "ok",
gc_fail: str | None = None,
) -> dict[str, str]:
env = os.environ.copy()
if gc_log is not None:
env["GC_ARGV_LOG"] = str(gc_log)
if bd_log is not None:
env["BD_ARGV_LOG"] = str(bd_log)
env["BD_STUB_MODE"] = bd_mode
if gc_fail is not None:
env["GC_STUB_FAIL"] = gc_fail
return env
def _managed_city_fixture(self, base: Path, *, rig: Path) -> dict[str, object]:
"""Build a managed-city surface invoke.sh accepts, anchored to the real
pack lock and the toolchain receipt (not the mutable marker).
gc and bd are stub binaries whose digests are recorded in a schema-2
receipt whose pinned pair matches deploy/gc/toolchain.lock.json. The
worktree helper is the real deploy/gc/worktree.sh, so feed's provenance
check passes and a real isolated worktree is prepared, fully offline.
"""
toolchain = base / "toolchain"
(toolchain / "bin").mkdir(parents=True)
gc_bin = toolchain / "bin/gc"
gc_bin.write_text(self._GC_STUB, encoding="utf-8")
gc_bin.chmod(0o755)
bd_bin = toolchain / "bin/bd"
bd_bin.write_text(self._BD_STUB, encoding="utf-8")
bd_bin.chmod(0o755)
receipt = {
"schema_version": 2,
"runtime": {
"gc": {"path": "bin/gc", "sha256": self._sha256_file(gc_bin)},
"bd": {"path": "bin/bd", "sha256": self._sha256_file(bd_bin)},
},
"pair": {
"status": "qualified",
"gc": {"source_commit": GC_COMMIT},
"bd": {"source_commit": BD_COMMIT},
},
}
(toolchain / "toolchain.json").write_text(json.dumps(receipt), encoding="utf-8")
city = base / "city"
(city / ".gc/agentops-packs/agentops-factory").mkdir(parents=True)
(city / ".gc/agentops-packs/agentops-factory/pack.toml").write_text("name = 'x'\n", encoding="utf-8")
(city / ".gc/scripts").mkdir(parents=True)
(city / ".beads").mkdir(parents=True)
(city / "city.toml").write_text("[workspace]\n", encoding="utf-8")
(city / ".beads/dolt-server.port").write_text("3306\n", encoding="utf-8")
installed_wt = city / ".gc/scripts/agentops-worktree"
installed_wt.write_bytes((DEPLOY / "worktree.sh").read_bytes())
installed_wt.chmod(0o755)
workers = base / "workers"
marker = city / ".gc/agentops-bootstrap.json"
marker.write_text(json.dumps({
"schema_version": 1,
"state": "ready",
"city": str(city.resolve()),
"rig": str(rig.resolve()),
"rig_name": "testrig",
"base_ref": "main",
"worktree_root": str(workers.resolve()),
"bead_database": "testdb",
"pack_snapshot": str((city / ".gc/agentops-packs/agentops-factory").resolve()),
"pack_sha256": self._tree_sha(city / ".gc/agentops-packs"),
"city_config_sha256": self._sha256_file(city / "city.toml"),
"toolchain": {
"gc": {"path": str(gc_bin.resolve()), "sha256": self._sha256_file(gc_bin)},
"bd": {"path": str(bd_bin.resolve()), "sha256": self._sha256_file(bd_bin)},
},
"telemetry": {"metrics_url": "", "logs_url": "", "sdk_disabled": True},
}), encoding="utf-8")
return {"city": city, "rig": rig, "workers": workers, "gc_bin": gc_bin, "bd_bin": bd_bin, "toolchain": toolchain}
def test_feed_verifies_bead_then_slings_full_flag_set(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space" # exercises argv quoting
base.mkdir()
repo, _ = self.make_repository(base)
fixture = self._managed_city_fixture(base, rig=repo)
gc_log = base / "gc.argv"
bd_log = base / "bd.argv"
env = self._invoke_env(gc_log=gc_log, bd_log=bd_log)
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "feed", "testrig-1",
env=env,
)
self.assertEqual(result.returncode, 0, result.stderr)
# Bead existence was verified in the rig store before any sling.
self.assertEqual(
bd_log.read_text(encoding="utf-8").splitlines(),
["-C", str(repo.resolve()), "show", "testrig-1", "--json", "---"],
)
# The full production sling flag set, one argv element per line.
expected_worktree = str(Path(os.path.realpath(fixture["workers"])) / "testrig-1")
self.assertEqual(gc_log.read_text(encoding="utf-8").splitlines(), [
"sling", "testrig/agentops.plan-reviewer", "testrig-1",
"--on", "agentops-experiment", "--nudge", "--json",
"--var", f"work_dir={expected_worktree}",
"--var", "plan_target=testrig/agentops.plan-reviewer",
"--var", "implement_target=testrig/agentops.implementer",
"--var", "validate_target=testrig/agentops.validator",
"--var", "refiner_target=testrig/agentops.refiner",
])
# The worktree was really prepared by the pinned pack helper.
self.assertTrue((Path(expected_worktree) / ".git").exists())
def test_feed_refuses_missing_bead_without_slinging(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space"
base.mkdir()
repo, _ = self.make_repository(base)
fixture = self._managed_city_fixture(base, rig=repo)
gc_log = base / "gc.argv"
env = self._invoke_env(gc_log=gc_log, bd_log=base / "bd.argv", bd_mode="notfound")
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "feed", "testrig-404",
env=env,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("bead not found in the rig store", result.stderr)
self.assertFalse(gc_log.exists(), "sling must not run for a missing bead")
self.assertFalse((Path(str(fixture["workers"])) / "testrig-404").exists())
def test_feed_refuses_bead_id_mismatch_without_slinging(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space"
base.mkdir()
repo, _ = self.make_repository(base)
fixture = self._managed_city_fixture(base, rig=repo)
gc_log = base / "gc.argv"
env = self._invoke_env(gc_log=gc_log, bd_log=base / "bd.argv", bd_mode="mismatch")
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "feed", "testrig-1",
env=env,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("exact single match", result.stderr)
self.assertFalse(gc_log.exists(), "sling must not run on an inexact match")
def test_feed_rejects_unsafe_bead_id(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary)
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "feed", "bad id;rm",
env=self._invoke_env(),
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("unsafe bead id", result.stderr)
def test_create_writes_managed_rig_store_and_prints_id(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space" # exercises argv quoting
base.mkdir()
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
bd_log = base / "bd.argv"
env = self._invoke_env(bd_log=bd_log)
plain = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]),
"create", "add a widget", "-d", "with a description", env=env,
)
self.assertEqual(plain.returncode, 0, plain.stderr)
self.assertEqual(plain.stdout, "testrig-9\n")
first_call = bd_log.read_text(encoding="utf-8").split("---\n")[0].splitlines()
self.assertEqual(first_call, [
"-C", str(rig.resolve()), "create", "--title", "add a widget",
"--type", "task", "--json", "-d", "with a description",
])
json_out = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]),
"create", "no description", "--json", env=env,
)
self.assertEqual(json_out.returncode, 0, json_out.stderr)
self.assertEqual(json.loads(json_out.stdout)["id"], "testrig-9")
# No description supplied -> no -d flag forwarded to bd (exact argv).
calls = [c for c in bd_log.read_text(encoding="utf-8").split("---\n") if c.strip()]
self.assertEqual(calls[-1].splitlines(), [
"-C", str(rig.resolve()), "create", "--title", "no description",
"--type", "task", "--json",
])
def test_mayor_start_wakes_the_named_shepherd_session(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space" # exercises argv quoting
base.mkdir()
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
gc_log = base / "gc.argv"
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "mayor", "start",
env=self._invoke_env(gc_log=gc_log),
)
self.assertEqual(result.returncode, 0, result.stderr)
# Native start verb: wake the city-scoped mayor named session.
self.assertEqual(
gc_log.read_text(encoding="utf-8").splitlines(),
["session", "wake", "agentops.mayor"],
)
def test_mayor_tell_delivers_a_notified_mail_message(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space"
base.mkdir()
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
gc_log = base / "gc.argv"
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]),
"mayor", "tell", "dispatch testrig-12",
env=self._invoke_env(gc_log=gc_log),
)
self.assertEqual(result.returncode, 0, result.stderr)
# The clean v1.3.5 primitive: a notified mail message to the mayor.
# The whole quoted instruction survives as one argv element.
self.assertEqual(
gc_log.read_text(encoding="utf-8").splitlines(),
["mail", "send", "--to", "agentops.mayor", "--notify", "-m", "dispatch testrig-12"],
)
def test_mayor_tell_refuses_empty_message_without_calling_gc(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space"
base.mkdir()
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
gc_log = base / "gc.argv"
for empty in ("", " "):
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]),
"mayor", "tell", empty,
env=self._invoke_env(gc_log=gc_log),
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("non-empty message", result.stderr)
self.assertFalse(gc_log.exists(), "no mail must be sent for an empty message")
def test_mayor_status_reports_the_socket_before_the_session_exists(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space"
base.mkdir()
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
gc_log = base / "gc.argv"
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "mayor", "status",
env=self._invoke_env(gc_log=gc_log),
)
self.assertEqual(result.returncode, 0, result.stderr)
# Status reads the native session list (stub returns none -> not started).
self.assertEqual(
gc_log.read_text(encoding="utf-8").splitlines(),
["session", "list", "--state", "all", "--json"],
)
expected_socket = "agentops-" + hashlib.sha256(
os.path.realpath(str(fixture["city"])).encode()
).hexdigest()[:20]
self.assertIn("not started", result.stdout)
self.assertIn(expected_socket, result.stdout)
def test_mayor_rejects_unknown_subcommand(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary)
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "mayor", "claim",
env=self._invoke_env(),
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("unknown mayor operation", result.stderr)
def test_mayor_status_fails_closed_when_gc_query_fails(self) -> None:
# Tri-state case (a): a failed gc query must NOT read as "no session".
# It exits nonzero with "status unavailable" and surfaces the stderr tail.
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary) / "has space"
base.mkdir()
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]), "mayor", "status",
env=self._invoke_env(gc_fail="session"),
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("status unavailable", result.stderr)
self.assertNotIn("not started", result.stdout)
def test_gc_stub_rejects_non_allowlisted_verbs(self) -> None:
# Fidelity guard: the pack must only drive gc through verified v1.3.5
# verbs. A non-allowlisted verb passthrough fails loudly.
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary)
rig = base / "rig"
rig.mkdir()
fixture = self._managed_city_fixture(base, rig=rig)
result = run(
str(DEPLOY / "invoke.sh"), "--city", str(fixture["city"]),
"--", "totally-made-up-verb",
env=self._invoke_env(),
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("non-allowlisted command", result.stderr)
def test_heartbeat_order_is_a_valid_cooldown_nudge(self) -> None:
# Finding 1: liveness. The pack ships a scheduled order that re-nudges the
# Mayor. Schema-lint it against the v1.3.5 order surface and the 2-5m band.
order_path = FACTORY / "orders/shepherd-heartbeat.toml"
self.assertTrue(order_path.is_file(), order_path)
order = tomllib.loads(order_path.read_text(encoding="utf-8"))["order"]
self.assertEqual(order["trigger"], "cooldown")
self.assertEqual(order.get("scope"), "city")
# Interval must be a Go duration in the 2-5 minute band.
self.assertRegex(order["interval"], r"^[1-9][0-9]*m$")
minutes = int(order["interval"][:-1])
self.assertGreaterEqual(minutes, 2)
self.assertLessEqual(minutes, 5)
# It nudges the Mayor via the same mail-send primitive, with --notify.
exec_cmd = order["exec"]
self.assertIn("gc mail send", exec_cmd)
self.assertIn("--to agentops.mayor", exec_cmd)
self.assertIn("--notify", exec_cmd)
def test_routed_bead_recovery_is_wake_not_resling(self) -> None:
# Finding 3 sanity: the docs must state that re-dispatching an already
# routed bead is a no-op, and that recovery is waking the worker session —
# so an agent does not cargo-cult a dead re-sling retry.
skill = (REPO_ROOT / "skills/using-gc/SKILL.md").read_text(encoding="utf-8")
self.assertIn("NO-OP", skill)
self.assertIn("gc session wake <run_target>", skill)
prompt = (FACTORY / "agents/mayor/prompt.template.md").read_text(encoding="utf-8")
self.assertIn("re-slinging a routed bead is a NO-OP", prompt)
self.assertIn("gc session wake <run_target>", prompt)
def test_mayor_mail_authority_has_no_pause_resume(self) -> None:
# Finding 5: mail authority is exactly dispatch/status; pause/resume is
# dropped (stateful, incompatible with wake_mode=fresh), and mail bodies
# are untrusted data, not instructions.
prompt = (FACTORY / "agents/mayor/prompt.template.md").read_text(encoding="utf-8")
self.assertNotIn("resume shepherding", prompt) # the old stateful verb
self.assertIn("no pause/resume", prompt.lower())
self.assertIn("UNTRUSTED DATA", prompt)
self.assertIn("NEVER executed", prompt)
def test_named_mayor_session_is_a_standing_always_shepherd(self) -> None:
# The 3.3 human-and-agent door needs a resident session: mode=always so
# the controller keeps it live with no demand, and no one_shot lifecycle
# so the pane stays attachable rather than exiting after one run.
pack = tomllib.loads((FACTORY / "pack.toml").read_text(encoding="utf-8"))
mayor_sessions = [
s for s in pack.get("named_session", []) if s.get("template") == "mayor"
]
self.assertEqual(len(mayor_sessions), 1)
self.assertEqual(mayor_sessions[0].get("scope"), "city")
self.assertEqual(mayor_sessions[0].get("mode"), "always")
agent = tomllib.loads((FACTORY / "agents/mayor/agent.toml").read_text(encoding="utf-8"))
self.assertNotIn("lifecycle", agent)
self.assertLessEqual(agent.get("min_active_sessions", 0), agent["max_active_sessions"])
def test_teardown_waits_for_scoped_drain_and_fails_with_exact_residue(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
city = root / "city"
marker = city / ".gc/agentops-bootstrap.json"
marker.parent.mkdir(parents=True)
fake_gc = root / "gc-stub.sh"
calls = root / "gc.log"
fake_gc.write_text(textwrap.dedent("""\
#!/bin/sh
printf '%s\\t%s\\n' "${GC_BIN:-}" "$*" >> "$FAKE_GC_LOG"
exit 0
"""), encoding="utf-8")
fake_gc.chmod(0o755)
marker.write_text(json.dumps({
"schema_version": 1,
"city": str(city.resolve()),
"toolchain": {"gc": {"path": str(fake_gc.resolve())}},
"supervisor_port": 65534,
"telemetry": {"sdk_disabled": True},
}), encoding="utf-8")
env = os.environ.copy()
env["FAKE_GC_LOG"] = str(calls)
draining = subprocess.Popen([
"python3", "-c", "import time; time.sleep(0.8)", str(city.resolve()),
])
started = time.monotonic()
result = run(
str(DEPLOY / "teardown.sh"), "--city", str(city), "--wait-timeout", "3",
env=env,
)
elapsed = time.monotonic() - started
draining.wait(timeout=3)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertGreaterEqual(elapsed, 0.3)
log = calls.read_text(encoding="utf-8")
self.assertIn(f"{fake_gc.resolve()}\t--city {city.resolve()} stop --force", log)
self.assertIn(f"{fake_gc.resolve()}\tsupervisor stop --wait --wait-timeout 3s", log)
stuck = subprocess.Popen([
"python3", "-c", "import time; time.sleep(10)", str(city.resolve()),
])
try:
failed = run(
str(DEPLOY / "teardown.sh"), "--city", str(city), "--wait-timeout", "1",
env=env,
)
self.assertNotEqual(failed.returncode, 0)
self.assertIn("managed city processes remain", failed.stderr)
self.assertIn(str(city.resolve()), failed.stderr)
finally:
stuck.terminate()
stuck.wait(timeout=3)
def test_worktree_helper_creates_one_isolated_idempotent_branch(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repo, _ = self.make_repository(root)
workers = root / "workers"
command = (
str(DEPLOY / "worktree.sh"), "prepare", "--repo", str(repo), "--root", str(workers),
"--bead", "age-test.1", "--base-ref", "main",
)
first = run(*command)
self.assertEqual(first.returncode, 0, first.stderr)
second = run(*command)
self.assertEqual(second.returncode, 0, second.stderr)
self.assertEqual(json.loads(first.stdout), json.loads(second.stdout))
receipt = json.loads(first.stdout)
self.assertEqual(receipt["branch"], "gc/age-test.1")
self.assertNotEqual(Path(receipt["worktree"]).resolve(), repo.resolve())
other = run(
str(DEPLOY / "worktree.sh"), "prepare", "--repo", str(repo), "--root", str(workers),
"--bead", "age-test.2", "--base-ref", "main",
)
self.assertEqual(other.returncode, 0, other.stderr)
other_receipt = json.loads(other.stdout)
self.assertEqual(other_receipt["branch"], "gc/age-test.2")
self.assertNotEqual(other_receipt["worktree"], receipt["worktree"])
self.assertNotEqual(
git("rev-parse", "--git-dir", cwd=Path(other_receipt["worktree"])).stdout.strip(),
git("rev-parse", "--git-dir", cwd=Path(receipt["worktree"])).stdout.strip(),
)
def test_refiner_uses_hosted_checks_and_respects_auto_merge(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repo, _ = self.make_repository(root)
workers = root / "workers"
prepared = run(
str(DEPLOY / "worktree.sh"), "prepare", "--repo", str(repo), "--root", str(workers),
"--bead", "age-refine.1", "--base-ref", "main",
)
self.assertEqual(prepared.returncode, 0, prepared.stderr)
worktree = Path(json.loads(prepared.stdout)["worktree"])
self.assertEqual(git("config", "user.name", "AgentOps Test", cwd=worktree).returncode, 0)
self.assertEqual(git("config", "user.email", "agentops-test@example.invalid", cwd=worktree).returncode, 0)
(worktree / "candidate.txt").write_text("validated\n", encoding="utf-8")
self.assertEqual(git("add", "candidate.txt", cwd=worktree).returncode, 0)
self.assertEqual(git("commit", "-m", "candidate", cwd=worktree).returncode, 0)
head = git("rev-parse", "HEAD", cwd=worktree).stdout.strip()
(repo / "moving-main.txt").write_text("unrelated\n", encoding="utf-8")
self.assertEqual(git("add", "moving-main.txt", cwd=repo).returncode, 0)
self.assertEqual(git("commit", "-m", "move main", cwd=repo).returncode, 0)
self.assertEqual(git("push", "origin", "main", cwd=repo).returncode, 0)
state = root / "gh-state"
log = root / "gh.log"
git_log = root / "git.log"
git_stub = root / "git-stub.py"
git_stub.write_text(textwrap.dedent("""\
#!/usr/bin/env python3
import os, pathlib, sys
with pathlib.Path(os.environ["GIT_LOG"]).open("a", encoding="utf-8") as handle:
handle.write(" ".join(sys.argv[1:]) + "\\n")
os.execv(os.environ["REAL_GIT"], [os.environ["REAL_GIT"], *sys.argv[1:]])
"""), encoding="utf-8")
git_stub.chmod(0o755)
gh = root / "gh-stub.py"
gh.write_text(textwrap.dedent("""\
#!/usr/bin/env python3
import json, os, pathlib, subprocess, sys
args = sys.argv[1:]
state = pathlib.Path(os.environ["GH_STATE"])
log = pathlib.Path(os.environ["GH_LOG"])
with log.open("a", encoding="utf-8") as handle: handle.write(" ".join(args) + "\\n")
if args[:2] == ["pr", "list"]: print("[]")
elif args[:2] == ["pr", "create"]: print("https://example.invalid/pull/7")
elif args[:2] == ["pr", "checks"]: pass
elif args[:2] == ["pr", "merge"]: state.write_text("MERGED", encoding="utf-8")
elif args[:2] == ["pr", "view"]:
merged = state.exists()
if args[-1] == "state,mergeStateStatus":
print(json.dumps({"state": "MERGED" if merged else "OPEN", "mergeStateStatus": "CLEAN"}))
else:
head = subprocess.check_output([os.environ["REAL_GIT"], "-C", os.environ["TEST_WORKTREE"], "rev-parse", "HEAD"], text=True).strip()
print(json.dumps({"number": 7, "url": "https://example.invalid/pull/7", "headRefOid": head, "state": "OPEN"}))
else: raise SystemExit("unexpected gh invocation: " + " ".join(args))
"""), encoding="utf-8")
gh.chmod(0o755)
env = os.environ.copy()
env.update({
"AGENTOPS_GC_GIT_BIN": str(git_stub), "AGENTOPS_GC_GH_BIN": str(gh),
"REAL_GIT": run("which", "git").stdout.strip(), "GIT_LOG": str(git_log),
"GH_STATE": str(state), "GH_LOG": str(log), "TEST_WORKTREE": str(worktree),
})
result = run(
str(DEPLOY / "refine.sh"), "--worktree", str(worktree), "--bead", "age-refine.1",
"--base-ref", "main", "--mode", "auto", env=env,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(result.stdout.strip(), f"empty receipt; stderr={result.stderr!r}")
receipt = json.loads(result.stdout)
self.assertEqual(receipt["mode"], "auto")
self.assertEqual(receipt["rebases"], 1)
self.assertEqual(receipt["validated_head"], head)
self.assertNotEqual(receipt["head"], head)
calls = log.read_text(encoding="utf-8")
self.assertIn("pr checks 7 --watch --fail-fast", calls)
self.assertIn("pr merge 7 --auto --squash --delete-branch", calls)
self.assertTrue(state.exists())
self.assertIn("fetch origin main --quiet", git_log.read_text(encoding="utf-8"))
prepared_manual = run(
str(DEPLOY / "worktree.sh"), "prepare", "--repo", str(repo), "--root", str(workers),
"--bead", "age-refine.2", "--base-ref", "main",
)
self.assertEqual(prepared_manual.returncode, 0, prepared_manual.stderr)
manual_worktree = Path(json.loads(prepared_manual.stdout)["worktree"])
self.assertEqual(git("config", "user.name", "AgentOps Test", cwd=manual_worktree).returncode, 0)
self.assertEqual(git("config", "user.email", "agentops-test@example.invalid", cwd=manual_worktree).returncode, 0)
(manual_worktree / "manual.txt").write_text("validated\n", encoding="utf-8")
self.assertEqual(git("add", "manual.txt", cwd=manual_worktree).returncode, 0)
self.assertEqual(git("commit", "-m", "manual candidate", cwd=manual_worktree).returncode, 0)
state.unlink()
log.write_text("", encoding="utf-8")
env["TEST_WORKTREE"] = str(manual_worktree)
manual = run(
str(DEPLOY / "refine.sh"), "--worktree", str(manual_worktree), "--bead", "age-refine.2",
"--base-ref", "main", "--mode", "manual", env=env,
)
self.assertEqual(manual.returncode, 0, manual.stderr)
self.assertEqual(json.loads(manual.stdout)["mode"], "manual")
self.assertNotIn("pr merge", log.read_text(encoding="utf-8"))
self.assertFalse(state.exists())
def test_required_telemetry_fails_before_city_creation(self) -> None:
if not PINNED_INTEGRATION:
self.skipTest("set GC_BIN and AGENTOPS_GC_INTEGRATION=1 for native integration")
gc_value = os.environ["GC_BIN"]
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repo, _ = self.make_repository(root)
city = root / "city"
self.initialize_beads(repo, Path(gc_value).resolve())
result = run(
str(DEPLOY / "bootstrap.sh"), "--city", str(city), "--rig", str(repo),
"--gc-bin", str(Path(gc_value).resolve()), "--telemetry-mode", "required",
"--otel-metrics-url", "http://127.0.0.1:9/metrics",
"--otel-logs-url", "http://127.0.0.1:9/logs",
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("required OTEL endpoints are unavailable", result.stderr)
self.assertFalse(city.exists())
@unittest.skipUnless(PINNED_INTEGRATION, "set GC_BIN and AGENTOPS_GC_INTEGRATION=1 for native integration")
def test_clean_bootstrap_is_idempotent_with_pinned_native_gc(self) -> None:
gc_bin = Path(os.environ["GC_BIN"]).resolve()
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repo, _ = self.make_repository(root)
city = root / "city"
source_bead = self.initialize_beads(repo, gc_bin)
command = (
str(DEPLOY / "bootstrap.sh"), "--city", str(city), "--rig", str(repo),
"--gc-bin", str(gc_bin), "--delivery-mode", "manual", "--telemetry-mode", "off",
)
first = run(*command)
self.assertEqual(first.returncode, 0, first.stderr)
marker_path = Path(first.stdout.strip())
before = marker_path.read_bytes()
second = run(*command)
self.assertEqual(second.returncode, 0, second.stderr)
self.assertEqual(marker_path.read_bytes(), before)
marker = json.loads(before)
self.assertEqual(marker["toolchain"]["gc"]["commit"], GC_COMMIT)
self.assertEqual(marker["toolchain"]["bd"]["commit"], BD_COMMIT)
self.assertEqual(marker["delivery_mode"], "manual")
self.assertEqual(marker["telemetry"]["mode"], "off")
self.assertEqual(marker["telemetry"]["sdk_disabled"], True)
native_env = os.environ.copy()
native_env.update({
"GC_HOME": str(city / ".gc-home"),
"PATH": f"{gc_bin.parent}:{native_env['PATH']}",
"OTEL_SDK_DISABLED": "true",
})
config = run(str(gc_bin), "--city", str(city), "--rig", marker["rig_name"], "config", "show", env=native_env)
self.assertEqual(config.returncode, 0, config.stderr)
compiled = run(
str(gc_bin), "--city", str(city), "--rig", marker["rig_name"],
"formula", "show", "agentops-experiment", "--json", env=native_env,
)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
self.assertEqual(json.loads(compiled.stdout)["name"], "agentops-experiment")
# Intake resolves to the rig-scoped planner (official single-bead
# dispatch), not the city-scoped Mayor.
planner_target = f"{marker['rig_name']}/agentops.plan-reviewer"
route = run(
str(gc_bin), "--city", str(city), "sling", planner_target, source_bead,
"--dry-run", "--json", env=native_env,
)
self.assertEqual(route.returncode, 0, route.stderr)
self.assertIn("agentops.plan-reviewer", json.loads(route.stdout)["target"])
status = run(str(DEPLOY / "invoke.sh"), "--city", str(city), "status", env=native_env)
self.assertEqual(status.returncode, 0, status.stderr)
self.assertEqual(json.loads(status.stdout)["schema_version"], "1")
doctor = run(str(gc_bin), "--city", str(city), "doctor", "--json", env=native_env)
self.assertEqual(doctor.returncode, 0, doctor.stdout + doctor.stderr)
self.assertEqual(json.loads(doctor.stdout)["blocking_failed"], 0)
teardown = run(str(DEPLOY / "teardown.sh"), "--city", str(city), "--wait-timeout", "10", env=native_env)
self.assertEqual(teardown.returncode, 0, teardown.stdout + teardown.stderr)
self.assertEqual(git("status", "--porcelain", cwd=repo).stdout, "")
if __name__ == "__main__":
unittest.main()