Let pushes go by an HTTPS token or an SSH deploy key
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 56s

The push credential is either a push-only account's token, handed to git
through a credential helper that names environment variables, never
values, or a deploy key in a short-lived ssh-agent. The remote URL
follows the credential. Configured as gitea.push_user and push_token, or
gitea.push_key.

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:
dongarandClaude Opus 5.5 committed 2026-10-02 21:59:56 +00:00
1 parent 761ebdb857
commit c931ac939b
15 files changed
+241 -81

No files matched your search

+9 -19
View File
@@ -5,12 +5,12 @@ Cleans up after itself (closes the PR, deletes the branch) so repeated runs
do not silt up the sandbox.
"""
import os
import uuid
from pathlib import Path
import pytest
from nisse_dev.agent import push_credential, remote_url
from nisse_dev.config import AgentConfig
from nisse_dev.execution import SSHHost
from nisse_dev.git import GitError, GitRepo
@@ -31,17 +31,6 @@ def intent_for(branch: str) -> Intent:
)
def push_key() -> str:
"""The sandbox's deploy key with write access: what the agent pushes with
(D4). NISSE_PUSH_KEY holds the private key itself (CI) or, since a .env
line can't hold one, the path to it (local runs)."""
value = os.environ.get("NISSE_PUSH_KEY", "")
assert value.strip(), "NISSE_PUSH_KEY is not set: the sandbox's deploy key"
if "PRIVATE KEY" not in value and Path(value).expanduser().is_file():
value = Path(value).expanduser().read_text()
return value if value.endswith("\n") else value + "\n"
def test_push_open_pr_and_reconcile(
tmp_path: Path, live_config: AgentConfig, gitea: GiteaClient, ssh_host: SSHHost
) -> None:
@@ -60,9 +49,10 @@ def test_push_open_pr_and_reconcile(
assert reconcile(intent_for(branch)) is False
# Pushed the way the agent does: from the build VM (here the throwaway
# sshd), over SSH, with the deploy key and never the API token.
key = push_key()
url = client.ssh_url(repo_path)
# sshd), with the push credential the config names, never the API token.
key = push_credential(live_config)
assert key is not None, "nisse-test.yaml names no push credential (D4)"
url = remote_url(client, repo_path, key)
repo = GitRepo(ssh_host, tmp_path / "work", known_hosts=str(tmp_path / "kh"))
repo.init()
repo.configure_identity(account, f"{account}@example.test")
@@ -71,7 +61,7 @@ def test_push_open_pr_and_reconcile(
pr = None
try:
repo.push(url, branch, key=key)
repo.push(url, branch, credential=key)
pr = client.open_pull_request(
repo_path,
head=branch,
@@ -89,7 +79,7 @@ def test_push_open_pr_and_reconcile(
assert reconcile(intent_for(branch)) is True
# D4 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 its push key
# must not be able to merge the PR it just opened, and its push credential
# 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"}
@@ -99,9 +89,9 @@ def test_push_open_pr_and_reconcile(
"the human gate"
)
with pytest.raises(GitError):
repo.push(url, "main", key=key)
repo.push(url, "main", credential=key)
finally:
if pr is not None:
client.close_pull_request(repo_path, pr.number)
repo.delete_remote_branch(url, branch, key=key)
repo.delete_remote_branch(url, branch, credential=key)
client.close()