M3.3: the trust dial (the agent merges approved PRs)
A project's trust.merge is operator (the default) or approved (L3, D5, #65). Set to approved, the harness, never the model, merges a PR in the PR watch once an approving review from a gitea.operators account is on its current commit, its checks pass (none at all counts; pending waits) and it's mergeable; the item then goes on as after any merge. Each reason to hold off (failed checks, conflicts, a refused merge) is said once per commit. Gitea enforces it: protection requires approvals from an operators-only whitelist and dismisses stale ones. At start, for each approved project, the agent checks by behaviour that its own unapproved merge is refused, with a throwaway branch and PR made through the API (never the workspace); if it isn't refused, the operator merges until a restart. - gitea: approvals, combined check status, a PR's head commit and mergeability, API-made branches and their deletion. - Test and bring-up repositories get the protection. - Tests: the decision (unit), the hard wall on a real Gitea (test_trust_live), an e2e test where the operator only approves, and a walk property, merges_only_approved, checked against a mutant that ignores which commit was approved. Closes #65 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
21cbf1682c
commit
ab469b60bb
17 files changed
+733
-11
No files matched your search
@@ -248,3 +248,74 @@ def test_assigned_at_is_the_last_assignment_across_pages() -> None:
|
||||
|
||||
assert client(handler).assigned_at("o/r", 6, "dev-agent") == "t4"
|
||||
assert client(handler).assigned_at("o/r", 6, "nobody") is None
|
||||
|
||||
|
||||
def test_approvals_are_the_standing_approving_reviews() -> None:
|
||||
reviews = [
|
||||
{"state": "APPROVED", "user": {"login": "don"}, "commit_id": "abc"},
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "don"},
|
||||
"commit_id": "old",
|
||||
"stale": True,
|
||||
},
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "x"},
|
||||
"commit_id": "abc",
|
||||
"dismissed": True,
|
||||
},
|
||||
{"state": "REQUEST_CHANGES", "user": {"login": "don"}, "commit_id": "abc"},
|
||||
{"state": "COMMENT", "user": {"login": "don"}, "commit_id": "abc"},
|
||||
]
|
||||
handler = lambda request: httpx.Response(200, json=reviews)
|
||||
approvals = client(handler).approvals("o/r", 3)
|
||||
assert [(a.author, a.commit, a.stale) for a in approvals] == [
|
||||
("don", "abc", False),
|
||||
("don", "old", True),
|
||||
]
|
||||
|
||||
|
||||
def test_checks_are_the_combined_status_or_none() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
assert request.url.path == "/api/v1/repos/o/r/commits/abc/status"
|
||||
return httpx.Response(200, json=answer)
|
||||
|
||||
answer: dict = {"state": "", "total_count": 0, "statuses": []}
|
||||
assert client(handler).checks("o/r", "abc") == "none"
|
||||
answer = {"state": "pending", "total_count": 1, "statuses": [{}]}
|
||||
assert client(handler).checks("o/r", "abc") == "pending"
|
||||
answer = {"state": "success", "total_count": 2, "statuses": [{}, {}]}
|
||||
assert client(handler).checks("o/r", "abc") == "success"
|
||||
|
||||
|
||||
def test_a_pull_request_carries_its_head_commit_and_mergeability() -> None:
|
||||
data = {
|
||||
**pr_json(5, "m-1"),
|
||||
"head": {"ref": "m-1", "sha": "f00"},
|
||||
"mergeable": True,
|
||||
}
|
||||
pr = client(lambda request: httpx.Response(200, json=data)).pull_request("o/r", 5)
|
||||
assert (pr.head_sha, pr.mergeable) == ("f00", True)
|
||||
|
||||
|
||||
def test_a_check_branch_is_made_through_the_api() -> None:
|
||||
seen: list = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
seen.append((request.method, request.url.path, request.content))
|
||||
return httpx.Response(201, json={})
|
||||
|
||||
gitea = client(handler)
|
||||
gitea.branch_with_file(
|
||||
"o/r", base="main", branch="chk", path="f", text="x", message="m"
|
||||
)
|
||||
gitea.delete_branch("o/r", "chk")
|
||||
body = json.loads(seen[0][2])
|
||||
assert (seen[0][0], seen[0][1]) == ("POST", "/api/v1/repos/o/r/contents/f")
|
||||
assert (body["branch"], body["new_branch"], body["content"]) == (
|
||||
"main",
|
||||
"chk",
|
||||
"eA==",
|
||||
)
|
||||
assert seen[1][:2] == ("DELETE", "/api/v1/repos/o/r/branches/chk")
|
||||
Reference in new issue
Block a user