mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
Hash workflow diff for fingerprint instead of per-file blobs
Using git show per changed file failed when a workflow file was deleted, because the blob no longer exists at the pushed head. That caused the guard to return None and skip approval for deletions. Hash the already-computed diff output instead; it captures adds, mods, deletes, and renames and cannot fail on a missing blob. Also adds a test that a deleted workflow file still requires approval. Refs: 98
This commit is contained in:
parent
57e8afa19f
commit
b0fa57fa48
2 changed files with 43 additions and 7 deletions
|
|
@ -340,13 +340,7 @@ def _workflow_change_for_push(backend: Any, parsed: ParsedGitPush) -> WorkflowPu
|
|||
if not diff.ok or not diff.output:
|
||||
return None
|
||||
|
||||
content_parts: list[str] = []
|
||||
for path in files:
|
||||
blob = _run_git(backend, root, f"show {shlex.quote(head)}:{shlex.quote(path)}")
|
||||
if not blob.ok:
|
||||
return None
|
||||
content_parts.append(blob.output)
|
||||
content_hash = _fingerprint({"contents": content_parts})
|
||||
content_hash = _fingerprint({"diff": diff.output})
|
||||
|
||||
remote = _run_git(backend, root, "config --get remote.origin.url")
|
||||
repo = _normalize_remote(_first_line(remote.output)) if remote.ok else ""
|
||||
|
|
|
|||
|
|
@ -407,6 +407,48 @@ async def test_rebased_workflow_push_keeps_fingerprint_for_same_diff(
|
|||
assert payload3["fingerprint"] != payload["fingerprint"]
|
||||
|
||||
|
||||
async def test_deleted_workflow_file_requires_approval(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
backend = _Backend(
|
||||
workflow_files=".github/workflows/ci.yml",
|
||||
diff_output="diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml\ndeleted file\n",
|
||||
)
|
||||
guard.SANDBOX_BACKENDS["thread-1"] = backend
|
||||
|
||||
async def fake_approved(thread_id: str, fingerprint: str) -> bool:
|
||||
return False
|
||||
|
||||
async def fake_find_approval(*args: Any, **kwargs: Any) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
async def fake_pending(thread_id: str, **kwargs: Any) -> tuple[dict[str, Any], bool]:
|
||||
return {"fingerprint": kwargs["fingerprint"], "status": "pending", "notified": False}, True
|
||||
|
||||
async def fake_post(*args: Any, **kwargs: Any) -> tuple[str, None]:
|
||||
return "1700000000.000300", None
|
||||
|
||||
async def fake_notified(*args: Any, **kwargs: Any) -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(guard, "workflow_push_approved", fake_approved)
|
||||
monkeypatch.setattr(guard, "find_workflow_push_approval", fake_find_approval)
|
||||
monkeypatch.setattr(guard, "ensure_workflow_push_pending", fake_pending)
|
||||
monkeypatch.setattr(guard, "post_slack_thread_reply_with_ts", fake_post)
|
||||
monkeypatch.setattr(guard, "mark_workflow_push_notified", fake_notified)
|
||||
|
||||
async def handler(_request: Any) -> ToolMessage:
|
||||
return ToolMessage(content="pushed", tool_call_id="call-1")
|
||||
|
||||
result = await guard.WorkflowPushGuardMiddleware().awrap_tool_call(_Request(), handler)
|
||||
|
||||
assert isinstance(result, ToolMessage)
|
||||
assert result.status == "error"
|
||||
payload = json.loads(str(result.content))
|
||||
assert payload["workflow_approval_status"] == "approval_required"
|
||||
assert payload["files"] == [".github/workflows/ci.yml"]
|
||||
|
||||
|
||||
async def test_approved_workflow_push_aborts_when_elevation_fails(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue