Files
codex-app-server/cloud/tests/test_server_audit_loop.py
T
host 3eac2a702e audit: wire loop driver to server endpoints and release gate (track 2)
Server-side execution for the track-1 pure driver (#260), no driver
semantics changed: audit verdict / fix completion / correction /
rebuild confirmation endpoints persist LoopState in audit_loop_state,
write every step through the #241/#247 ledger path (ledger first,
state second, fail-closed), derive fix/rebuild subtasks, and block
merge/release via the existing #247 chain reader. POST
/tasks/{id}/release follows the #259 contract (global-admin write,
fresh hard-gate evidence, merged status, multi-source 409 with no
audit write on block); deploy.py stays outside task gates.
2026-11-03 02:49:25 +00:00

458 lines
18 KiB
Python

"""HTTP end-to-end tests for the audit-loop wiring (track 2).
Same harness shape as test_server_merge.py: a real server on a
temp DB, no production tasks, no GitHub. Covers the automatic
chain through HTTP (fail -> fix subtask -> merge 409 -> fix
completion -> full re-audit pass -> merge 200 -> release),
plus the release contract, acceptance-confirmation flow, and
the quota / correction boundaries.
"""
import http.client
import json
import os
import sqlite3
import sys
import tempfile
import threading
import unittest
from pathlib import Path
try:
from cloud.auth import AuthStore
from cloud.server import create_server
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.auth import AuthStore
from cloud.server import create_server
MODULE = "mod-http"
VERSION = "v1"
EXTRA_PASS = {
"security_unconditional": True,
"regression_exact_artifact": True,
"demo_runnable": True,
"known_deviation_due_todo": True,
"audit_signer_separation": True,
"qa_failed_links_rerun": True,
}
def hard_body():
return {
"files_changed": ["apps/web/a.py"],
"owned_dir": "apps/web",
"branch_protection": "protected",
"ci_status": "success",
"ci_checks": [{"name": "ci", "status": "success"}],
"required_checks": ["ci"],
"contract_version": VERSION,
"contract_frozen": True,
"coordinator_approved": True,
"unresolved_review_comments": 0,
}
def fail_body(source="audit"):
return {
"module_id": MODULE,
"contract_version": VERSION,
"verdict": {
"verdict": "fail",
"source": source,
"verdict_text": "check x failed",
"failures": [{"check": "ci_green", "detail": "x"}],
"passed_items": ["a", "b", "c"],
"contract_version": VERSION,
},
}
def pass_body():
return {
"module_id": MODULE,
"contract_version": VERSION,
"verdict": {
"verdict": "pass",
"source": "audit",
"verdict_text": "all green",
"failures": [],
"passed_items": ["a", "b", "c"],
"contract_version": VERSION,
},
}
class AuditLoopServerTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
self._saved_env = {
k: os.environ.get(k) for k in (
"CLOUD_MERGE_GATE_MODE", "CLOUD_BRANCH_PROTECTION",
"CODEX_GLOBAL_ADMINS",
"CLOUD_AUDIT_LOOP_COST_THRESHOLD_USD")}
os.environ.pop("CLOUD_MERGE_GATE_MODE", None)
os.environ.pop("CLOUD_BRANCH_PROTECTION", None)
os.environ.pop("CLOUD_AUDIT_LOOP_COST_THRESHOLD_USD", None)
self.auth = AuthStore(self.db_path)
self.owner_id, self.owner_key = self.auth.create_user("owner")
self.maint_id, self.maint_key = self.auth.create_user("maint")
self.contrib_id, self.contrib_key = self.auth.create_user(
"contrib")
self.outsider_id, self.outsider_key = self.auth.create_user(
"outsider")
os.environ["CODEX_GLOBAL_ADMINS"] = self.owner_id
self.server = create_server(self.db_path, "127.0.0.1", 0)
self.port = self.server.server_address[1]
self.thread = threading.Thread(
target=self.server.serve_forever, daemon=True)
self.thread.start()
status, self.project = self._req(
"POST", "/projects", {"name": "demo"}, self.owner_key)
self.assertEqual(status, 201)
self.auth.set_member(
self.project["id"], self.owner_id, self.maint_id,
"maintainer")
self.auth.set_member(
self.project["id"], self.owner_id, self.contrib_id,
"contributor")
def tearDown(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
for key, value in self._saved_env.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
try:
self.auth.conn.close()
except Exception:
pass
self.tmp.cleanup()
def _req(self, method, path, body=None, key=None):
conn = http.client.HTTPConnection(
"127.0.0.1", self.port, timeout=10)
hdrs = {}
payload = None
if body is not None:
payload = json.dumps(body).encode("utf-8")
hdrs["Content-Type"] = "application/json"
if key is not None:
hdrs["X-API-Key"] = key
try:
conn.request(method, path, body=payload, headers=hdrs)
resp = conn.getresponse()
data = resp.read()
finally:
conn.close()
try:
parsed = json.loads(data.decode("utf-8")) if data else None
except ValueError:
parsed = None
return resp.status, parsed
def _completed_task(self):
status, task = self._req(
"POST", "/tasks",
{"project_id": self.project["id"], "prompt": "x"},
self.owner_key)
self.assertEqual(status, 201)
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"UPDATE tasks SET status = 'completed' WHERE id = ?",
(task["id"],))
conn.commit()
finally:
conn.close()
return task
def _task_status(self, task_id):
status, got = self._req(
"GET", "/tasks/%s" % task_id, key=self.owner_key)
self.assertEqual(status, 200)
return got["status"]
def _chain_count(self):
conn = sqlite3.connect(self.db_path)
try:
return conn.execute(
"SELECT COUNT(*) FROM audit_ledger").fetchone()[0]
finally:
conn.close()
def test_full_chain_fail_fix_reaudit_pass_merge_release(self):
task = self._completed_task()
status, first = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(first["action_kind"], "pushback")
self.assertEqual(first["subtask"]["kind"], "fix")
fix_task = first["subtask_task"]
self.assertIsNotNone(fix_task)
self.assertEqual(fix_task["status"], "queued")
# Original task stays completed: fail blocks, it does
# not roll the task back into a re-run.
self.assertEqual(self._task_status(task["id"]), "completed")
# Merge is blocked by the chain pushback even with a
# fully green hard gate.
status, blocked = self._req(
"POST", "/tasks/%s/merge" % task["id"],
hard_body(), self.owner_key)
self.assertEqual(status, 409)
self.assertIn("audit_pushback_blocked",
blocked["merge_gate"]["blocks"])
# Fix completion on the SUBTASK id, with automated
# evidence + a real auditor re-audit verdict, unblocks
# the PARENT task: chain rows are attributed to the root
# task (forked_from walk), not to the transient subtask.
fix_body = {
"module_id": MODULE,
"contract_version": VERSION,
"fix_result": {"reaudit_verdict": pass_body()["verdict"]},
"automated": {"hard_gate": hard_body(),
"extra_checks": EXTRA_PASS},
}
status, fixed = self._req(
"POST", "/tasks/%s/audit-fix" % fix_task["id"],
fix_body, self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(fixed["action_kind"], "pass")
self.assertTrue(
fixed["automated_checks"]["automated_checks_pass"])
# Loop state is durable and readable back.
status, loop = self._req(
"GET", "/tasks/%s/audit-loop?module_id=%s"
"&contract_version=%s"
% (task["id"], MODULE, VERSION),
key=self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(loop["loop_state"]["status"], "passed")
self.assertFalse(loop["audit_pushback"]["merge_blocked"])
# Now merge passes, and a merge 200 is never a release
# input: release with no fresh evidence is 409.
status, merged = self._req(
"POST", "/tasks/%s/merge" % task["id"],
hard_body(), self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(merged["status"], "merged")
status, rel = self._req(
"POST", "/tasks/%s/release" % task["id"],
{}, self.owner_key)
self.assertEqual(status, 409)
self.assertEqual(rel["release_gate"]["code"],
"release_hard_gate_evidence_missing")
# Release with fresh evidence passes for a global admin.
status, rel = self._req(
"POST", "/tasks/%s/release" % task["id"],
hard_body(), self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(rel["release_gate"]["code"], "release_allowed")
def test_release_409_writes_no_audit_and_multi_source(self):
task = self._completed_task()
self._req("POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.owner_key)
before = self._chain_count()
# Not merged + hard gate failing + pushback blocked:
# all three sources listed, first code is task_status.
bad_hard = hard_body()
bad_hard["ci_status"] = "failure"
status, rel = self._req(
"POST", "/tasks/%s/release" % task["id"],
bad_hard, self.owner_key)
self.assertEqual(status, 409)
self.assertEqual(rel["release_gate"]["blocked_sources"],
["task_status", "hard_gate", "audit_pushback"])
self.assertEqual(self._chain_count(), before)
def test_release_requires_global_admin_write(self):
task = self._completed_task()
# Maintainer with merge rights cannot release: release
# is a global-admin WRITE, not a project permission.
status, _ = self._req(
"POST", "/tasks/%s/release" % task["id"],
hard_body(), self.maint_key)
self.assertEqual(status, 403)
# A global admin who is not a project member CAN release
# (identity is CODEX_GLOBAL_ADMINS only, per #259).
os.environ["CODEX_GLOBAL_ADMINS"] = self.outsider_id
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"UPDATE tasks SET status = 'merged' WHERE id = ?",
(task["id"],))
conn.commit()
finally:
conn.close()
status, rel = self._req(
"POST", "/tasks/%s/release" % task["id"],
hard_body(), self.outsider_key)
self.assertEqual(status, 200)
self.assertEqual(rel["release_gate"]["code"], "release_allowed")
def test_acceptance_pending_no_subtask_confirm_flow(self):
task = self._completed_task()
for _ in range(4):
status, result = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(source="acceptance"), self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(result["action_kind"],
"pending_user_confirmation")
self.assertIsNone(result["subtask"])
self.assertIsNone(result["subtask_task"])
self.assertEqual(result["notification"]["party"], "user")
# A coordinator-style call (confirmed_by != caller) is
# refused: nobody confirms on a user's behalf.
status, _ = self._req(
"POST", "/tasks/%s/audit-rebuild-confirm" % task["id"],
{"module_id": MODULE, "contract_version": VERSION,
"user_confirmed": True, "confirmed_by": "someone-else"},
self.owner_key)
self.assertEqual(status, 403)
# Missing explicit flag is refused, not defaulted.
status, _ = self._req(
"POST", "/tasks/%s/audit-rebuild-confirm" % task["id"],
{"module_id": MODULE, "contract_version": VERSION,
"confirmed_by": self.owner_id},
self.owner_key)
self.assertEqual(status, 400)
# Real user confirmation creates the rebuild subtask.
status, confirmed = self._req(
"POST", "/tasks/%s/audit-rebuild-confirm" % task["id"],
{"module_id": MODULE, "contract_version": VERSION,
"user_confirmed": True,
"confirmed_by": self.owner_id,
"old_impl_ref": {"archive_ref": "task:%s:pre-rebuild"
% task["id"]}},
self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(confirmed["action_kind"], "rebuild")
self.assertEqual(confirmed["subtask"]["kind"], "rebuild")
self.assertIsNotNone(confirmed["subtask_task"])
def test_exactly_three_pushback_fourth_rebuild_http(self):
task = self._completed_task()
for index in range(3):
status, result = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(result["action_kind"], "pushback")
body = fail_body()
body["old_impl_ref"] = {"archive_ref": "task:%s:pre-rebuild"
% task["id"]}
status, result = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
body, self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(result["action_kind"], "rebuild")
self.assertEqual(result["loop_state"]["rebuild_no"], 1)
def test_correction_to_pass_still_blocks_until_fresh_pass(self):
task = self._completed_task()
self._req("POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.owner_key)
conn = sqlite3.connect(self.db_path)
try:
entry_id = conn.execute(
"SELECT id FROM audit_ledger WHERE action = ?"
" ORDER BY id ASC", ("audit_verdict",)).fetchone()[0]
finally:
conn.close()
status, corrected = self._req(
"POST", "/tasks/%s/audit-correction" % task["id"],
{"module_id": MODULE, "contract_version": VERSION,
"correction": {
"original_verdict": "fail",
"corrected_verdict": "pass",
"reviewer": "reviewer-1",
"original_entry_id": entry_id,
"findings": ["misjudged"]}},
self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(corrected["correction_action"],
"schedule_reaudit")
self.assertTrue(corrected["merge_blocked"])
status, _ = self._req(
"POST", "/tasks/%s/merge" % task["id"],
hard_body(), self.owner_key)
self.assertEqual(status, 409)
# A fresh passing verdict (not the correction) unblocks.
status, _ = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
pass_body(), self.owner_key)
self.assertEqual(status, 200)
status, merged = self._req(
"POST", "/tasks/%s/merge" % task["id"],
hard_body(), self.owner_key)
self.assertEqual(status, 200)
def test_fix_without_reaudit_is_refused_and_automation_not_pass(self):
task = self._completed_task()
status, first = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.owner_key)
self.assertEqual(status, 200)
fix_task = first["subtask_task"]
# No reaudit verdict at all: 400, loop still blocked.
status, _ = self._req(
"POST", "/tasks/%s/audit-fix" % fix_task["id"],
{"module_id": MODULE, "contract_version": VERSION,
"fix_result": {"fix_diff": {"summary": "changed"}}},
self.owner_key)
self.assertEqual(status, 400)
# Automated checks all pass but the auditor verdict is a
# fail: the loop stays blocked (automation != audit).
status, fixed = self._req(
"POST", "/tasks/%s/audit-fix" % fix_task["id"],
{"module_id": MODULE, "contract_version": VERSION,
"fix_result": {"reaudit_verdict":
fail_body()["verdict"]},
"automated": {"hard_gate": hard_body(),
"extra_checks": EXTRA_PASS}},
self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(fixed["action_kind"], "pushback")
self.assertTrue(fixed["merge_blocked"])
def test_quota_zero_blocks_without_consuming_round(self):
task = self._completed_task()
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"UPDATE user_quotas SET quota_usd_micros = 0"
" WHERE user_id = ?", (self.owner_id,))
conn.commit()
finally:
conn.close()
status, result = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(result["action_kind"], "quota_blocked")
# No loop state was saved: nothing was consumed.
status, _ = self._req(
"GET", "/tasks/%s/audit-loop?module_id=%s"
"&contract_version=%s"
% (task["id"], MODULE, VERSION),
key=self.owner_key)
self.assertEqual(status, 404)
def test_verdict_forbidden_for_contributor(self):
task = self._completed_task()
status, _ = self._req(
"POST", "/tasks/%s/audit-verdict" % task["id"],
fail_body(), self.contrib_key)
self.assertEqual(status, 403)
if __name__ == "__main__":
unittest.main()