"""Local supervisor regressions; fixtures do not claim Blender coverage.""" import contextlib import io import unittest from unittest.mock import patch import test_wbv2_autopilot as fixtures from wbv2_autopilot import sha, write_json class LocalWorkflowTests(unittest.TestCase): setUp = fixtures.SupervisorTests.setUp file = fixtures.SupervisorTests.file ready_result = fixtures.SupervisorTests.ready_result def local_contract(self): self.policy["modelExecution"] = "disabled" result = self.ready_result(self.runner.directory / "old") contract_path = "local-contract.json" self.file("my-tools/wbv2_validation.py", "# fixture\n") self.policy["localWorkflows"] = {self.job["id"]: contract_path} contract = {"task": self.task, "job": self.job["id"], "parentSha256": self.context["parentSha256"], "result": result} write_json(self.root / contract_path, contract) return contract def test_local_workflow_never_calls_model_and_writes_fresh_receipts(self): self.local_contract() with patch.object(self.runner, "worker", side_effect=AssertionError("model forbidden")), patch.object(self.runner, "worker_prompt", side_effect=AssertionError("prompt forbidden")), contextlib.redirect_stdout(io.StringIO()): self.assertTrue(self.runner.attempt(self.context, self.job)) self.assertTrue(self.runner.receipt_valid(self.runner.state["jobs"][self.job["id"]])) self.assertEqual(0, self.runner.state["tokensUsed"]) def test_model_execution_is_denied_at_all_entrypoints(self): self.policy["modelExecution"] = "disabled" with self.assertRaisesRegex(ValueError, "disabled"): self.runner.worker("source", self.runner.directory) with self.assertRaisesRegex(ValueError, "disabled"): self.runner.worker_prompt(self.context, self.job, self.runner.directory, {}) with self.assertRaisesRegex(ValueError, "disabled"): self.runner.run_process(["codex", "exec"], self.root, self.runner.directory / "model.log", 1) def test_local_contract_cannot_advance_or_change_parent(self): contract = self.local_contract() contract["parentSha256"] = "0" * 64 write_json(self.root / "local-contract.json", contract) with self.assertRaisesRegex(ValueError, "parent changed"): self.runner.local_result(self.context, self.job, self.runner.directory / "fresh") def test_scheduler_selects_only_registered_unverified_local_jobs(self): self.local_contract() unknown = {"id": "NO-LOCAL-CONTRACT"} self.assertEqual(self.job, self.runner.choose_job([unknown, self.job])) self.runner.state["jobs"][self.job["id"]] = {"state": "blocked", "localContractSha256": sha(self.root / "local-contract.json")} self.assertIsNone(self.runner.choose_job([unknown, self.job])) def test_local_plan_discovery_and_changed_contract_retry(self): contract = self.local_contract() self.policy["localWorkflows"] = {} location = self.runner.directory / "local-plans" / (self.job["id"] + ".json") write_json(location, contract) self.assertEqual(self.job, self.runner.choose_job([self.job])) self.runner.state["jobs"][self.job["id"]] = {"state": "blocked", "localContractSha256": sha(location)} self.assertIsNone(self.runner.choose_job([self.job])) contract["maintenanceRevision"] = 2 write_json(location, contract) self.assertEqual(self.job, self.runner.choose_job([self.job])) def test_behavior_pending_does_not_starve_the_next_workflow(self): contract = self.local_contract() next_job = {"id": "NEXT-JOB", "ownedPaths": []} self.policy["localWorkflows"]["NEXT-JOB"] = "next-contract.json" write_json(self.root / "next-contract.json", {**contract, "job": "NEXT-JOB"}) self.runner.state["jobs"][self.job["id"]] = { "state": "behavior_pending", "localContractSha256": sha(self.root / "local-contract.json")} self.assertEqual(next_job, self.runner.choose_job([self.job, next_job])) contract["revision"] = 2 write_json(self.root / "local-contract.json", contract) self.assertEqual(self.job, self.runner.choose_job([self.job, next_job])) def test_model_process_is_denied_even_with_legacy_policy(self): self.policy["modelExecution"] = "enabled" with self.assertRaisesRegex(ValueError, "disabled"): self.runner.run_process(["codex", "exec"], self.root, self.runner.directory / "model.log", 1) def test_receipt_producer_mismatch_is_reported_before_cache(self): self.local_contract() script = (self.root / "check.py").read_text() self.file("check.py", script.replace("'unitIds': ['operator:TEST']", "'unitIds': ['operator:WRONG']")) with contextlib.redirect_stdout(io.StringIO()): self.assertFalse(self.runner.attempt(self.context, self.job)) reason = self.runner.state["jobs"][self.job["id"]]["reason"] self.assertIn("binding mismatch before cache", reason) self.assertIn("operator:WRONG", reason) def test_missing_receipt_names_the_producing_check(self): self.local_contract() self.file("check.py", "print('No receipt emitted')\n") with contextlib.redirect_stdout(io.StringIO()): self.assertFalse(self.runner.attempt(self.context, self.job)) reason = self.runner.state["jobs"][self.job["id"]]["reason"] self.assertIn("missing runtime receipt: case=positive check=native", reason) def test_local_environment_is_applied_and_supervisor_receipts_win(self): self.local_contract() contract = self.runner.local_result(self.context, self.job, self.runner.directory / "copy") contract["checks"][0]["argv"] = ["env", "WEB_TEST_PORT=5297", "WBV2_RECEIPT_DIR=/tmp/stale", "python3", "check.py"] write_json(self.root / "local-contract.json", {"task": self.task, "job": self.job["id"], "parentSha256": self.context["parentSha256"], "result": contract}) with patch.object(self.runner, "run_process", wraps=self.runner.run_process) as run, contextlib.redirect_stdout(io.StringIO()): self.assertTrue(self.runner.attempt(self.context, self.job)) self.assertEqual("5297", run.call_args.kwargs["env"]["WEB_TEST_PORT"]) self.assertNotEqual("/tmp/stale", run.call_args.kwargs["env"]["WBV2_RECEIPT_DIR"]) if __name__ == "__main__": unittest.main()