Externalise plans to Gitea and accept approvals there (A4, Part H)
- approvals.py: PlanBoard opens one issue per project and posts each plan revision as a comment carrying its rev tag. The state store keeps only a pointer, so gate state can be re-derived from Gitea after a state-volume loss — external truth wins - Gitea comments are a second approval source: revision binding, the typed word approve, and first-approval-wins work exactly as over transport, with a per-channel allowlist because a Gitea login is not a Matrix MXID - Outcomes are echoed to the other channel, so the two records cannot silently disagree about what was approved - A Gitea outage degrades durability of the record, not the ability to approve: planning proceeds and Matrix remains a complete channel - Live test now asserts the server refuses the agent's own merge and its push to the protected branch. That property was previously true only because our code did not attempt it; branch protection makes it structural, and this test fails if the protection is ever relaxed - Fixed a race in the process-group test: killing a group is asynchronous and the grandchild is an orphan, so its exit is not synchronised with our return; the test now waits for death Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
affdb9e8aa
commit
d508e9a9b7
6 files changed
+409
-4
No files matched your search
@@ -12,7 +12,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from nisse_dev.envfile import load_env_file
|
||||
from nisse_dev.git import GitRepo, authenticated_url
|
||||
from nisse_dev.git import GitError, GitRepo, authenticated_url
|
||||
from nisse_dev.gitea import GiteaClient, pull_request_reconciler
|
||||
from nisse_dev.state import Intent
|
||||
|
||||
@@ -88,6 +88,19 @@ def test_push_open_pr_and_reconcile(tmp_path: Path) -> None:
|
||||
|
||||
# Now the same reconciler must see the effect and refuse to repeat it.
|
||||
assert reconcile(intent_for(branch)) is True
|
||||
|
||||
# A4 is only real if the server enforces it. The agent's own token
|
||||
# must not be able to merge the PR it just opened, and must not be
|
||||
# able to push to the protected default branch at all.
|
||||
merged = client._http.post(
|
||||
f"/repos/{repo_path}/pulls/{pr.number}/merge", json={"Do": "merge"}
|
||||
)
|
||||
assert merged.status_code >= 400, (
|
||||
"the agent merged its own PR: branch protection is not enforcing "
|
||||
"the human gate"
|
||||
)
|
||||
with pytest.raises(GitError):
|
||||
repo.push(url, "main", token=token)
|
||||
finally:
|
||||
if pr is not None:
|
||||
client.close_pull_request(repo_path, pr.number)
|
||||
|
||||
Reference in new issue
Block a user