Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions deploy/coven-github/coven_github_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ def trigger_enabled(policy, trigger):
return trigger in set(enabled or [])


def event_trigger_key(event_name, payload):
action = str(payload.get("action") or "").strip()
return "{}.{}".format(event_name, action) if action else event_name


def build_task_from_event(event_name, delivery_id, payload, policy):
repository = payload.get("repository") or {}
installation = payload.get("installation") or {}
Expand All @@ -315,10 +320,12 @@ def build_task_from_event(event_name, delivery_id, payload, policy):
if event_name == "issue_comment":
issue = payload.get("issue") or {}
comment = payload.get("comment") or {}
if payload.get("action") != "created":
return ignored(base, "unsupported_issue_comment_action")
if not mentioned(comment.get("body"), policy):
return ignored(base, "issue_comment_without_mention")
if not trigger_enabled(policy, "issue_mention"):
return ignored(base, "issue_mention_not_enabled")
if not trigger_enabled(policy, "issue_comment.created"):
return ignored(base, "issue_comment_not_enabled")
if issue.get("pull_request"):
base.update(
{
Expand Down Expand Up @@ -352,9 +359,11 @@ def build_task_from_event(event_name, delivery_id, payload, policy):
if event_name == "pull_request_review_comment":
comment = payload.get("comment") or {}
pull_request = payload.get("pull_request") or {}
if payload.get("action") != "created":
return ignored(base, "unsupported_pr_review_comment_action")
if not mentioned(comment.get("body"), policy):
return ignored(base, "pr_review_comment_without_mention")
if not trigger_enabled(policy, "pr_review_comment"):
if not trigger_enabled(policy, "pull_request_review_comment.created"):
return ignored(base, "pr_review_comment_not_enabled")
base.update(
{
Expand All @@ -380,13 +389,13 @@ def build_task_from_event(event_name, delivery_id, payload, policy):
action = payload.get("action")
if action not in ("assigned", "labeled"):
return ignored(base, "unsupported_issue_action")
if action == "assigned" and not trigger_enabled(policy, "issue_assigned"):
if action == "assigned" and not trigger_enabled(policy, "issues.assigned"):
return ignored(base, "issue_assigned_not_enabled")
if action == "assigned" and not issue_assigned_to_bot(issue, policy):
return ignored(base, "issue_assigned_to_unmanaged_user")
if action == "labeled" and not labels_include_trigger(issue.get("labels"), policy):
return ignored(base, "issue_label_not_enabled")
if action == "labeled" and not trigger_enabled(policy, "issue_label"):
if action == "labeled" and not trigger_enabled(policy, "issues.labeled"):
return ignored(base, "issue_label_not_enabled")
base.update(
{
Expand Down Expand Up @@ -532,6 +541,21 @@ def route_delivery(event_name, delivery_id, payload, debug):
"reason": "no_policy_for_installation_repo",
}

trigger = event_trigger_key(event_name, payload)
if not trigger_enabled(policy, trigger):
delivery["state"] = "ignored"
delivery["routing_result"] = "trigger_not_enabled"
delivery["installation_id"] = installation_id or delivery.get("installation_id")
delivery["repository_id"] = repo_id or delivery.get("repository_id")
write_json_atomic(delivery_file, delivery)
return {
"ok": True,
"action": "ignored",
"delivery_id": delivery_id,
"reason": "trigger_not_enabled",
"trigger": trigger,
}

task = build_task_from_event(event_name, delivery_id, payload, policy)
task["policy_snapshot"] = {
"enabled_triggers": policy.get("enabled_triggers") or [],
Expand Down
204 changes: 204 additions & 0 deletions deploy/coven-github/test_coven_github_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,210 @@ def test_route_signed_delivery_reports_missing_secret(self):
self.assertEqual(result["status"], 500)
self.assertIn("GITHUB_WEBHOOK_SECRET", result["error"])

def test_route_delivery_enforces_exact_trigger_policy(self):
adapter = load_adapter()
policy = {
"enabled_triggers": [
"issues.labeled",
"issue_comment.created",
"pull_request_review_comment.created",
],
"trigger_labels": ["coven:fix"],
"bot_usernames": ["coven-cody[bot]"],
"familiar": {
"id": "cody",
"display_name": "Cody",
"model": "openai/gpt-5.5",
"skills": [],
},
"publication": {"mode": "record_only"},
}

with tempfile.TemporaryDirectory() as tmp:
state_dir = Path(tmp)
adapter.DELIVERIES_DIR = state_dir / "deliveries"
adapter.TASKS_DIR = state_dir / "tasks"
adapter.repo_policy = lambda payload: ("123456", "987654321", policy)
ran_tasks = []
adapter.run_task = lambda task_id, debug: ran_tasks.append(task_id)

result = adapter.route_delivery(
"issues",
"delivery-issue-opened",
{
"action": "opened",
"installation": {"id": 123456},
"repository": {
"id": 987654321,
"full_name": "OpenCoven/example",
"clone_url": "https://github.com/OpenCoven/example.git",
"default_branch": "main",
},
"issue": {
"number": 43,
"title": "Installer is slow",
"body": "A diagnostic issue, not a bot task.",
"labels": [],
"assignees": [],
},
},
lambda message: None,
)

self.assertEqual(result["action"], "ignored")
self.assertEqual(result["reason"], "trigger_not_enabled")
self.assertEqual(result["trigger"], "issues.opened")
self.assertTrue(adapter.delivery_path("delivery-issue-opened").exists())
self.assertFalse(adapter.task_path("delivery-issue-opened").exists())
self.assertEqual(ran_tasks, [])

allowed = adapter.route_delivery(
"issues",
"delivery-issue-labeled",
{
"action": "labeled",
"installation": {"id": 123456},
"repository": {
"id": 987654321,
"full_name": "OpenCoven/example",
"clone_url": "https://github.com/OpenCoven/example.git",
"default_branch": "main",
},
"issue": {
"number": 44,
"title": "Fix it",
"body": "Please fix it.",
"labels": [{"name": "coven:fix"}],
},
},
lambda message: None,
)

self.assertEqual(allowed["action"], "accepted")
self.assertTrue(adapter.task_path("delivery-issue-labeled").exists())
self.assertEqual(ran_tasks, ["delivery-issue-labeled"])

def test_event_trigger_key_preserves_actionless_events(self):
adapter = load_adapter()

self.assertEqual(adapter.event_trigger_key("push", {}), "push")
self.assertEqual(
adapter.event_trigger_key("issues", {"action": "labeled"}),
"issues.labeled",
)

def test_webhook_trigger_names_cover_all_changed_routes(self):
adapter = load_adapter()
common = {
"installation": {"id": 123456},
"repository": {
"id": 987654321,
"full_name": "OpenCoven/example",
"clone_url": "https://github.com/OpenCoven/example.git",
"default_branch": "main",
},
}
familiar = {
"id": "cody",
"display_name": "Cody",
"model": "openai/gpt-5.5",
"skills": [],
}
cases = (
(
"issue_comment",
{
**common,
"action": "created",
"issue": {"number": 45},
"comment": {"body": "@coven-cody[bot] please help"},
},
"issue_comment.created",
"issue_mention",
),
(
"pull_request_review_comment",
{
**common,
"action": "created",
"pull_request": {"number": 46},
"comment": {"body": "@coven-cody[bot] please fix this"},
},
"pull_request_review_comment.created",
"pr_review_comment",
),
(
"issues",
{
**common,
"action": "assigned",
"issue": {
"number": 47,
"assignee": {"login": "coven-cody[bot]"},
},
},
"issues.assigned",
"issue_assigned",
),
)

for event_name, payload, enabled_trigger, expected_trigger in cases:
with self.subTest(event_name=event_name):
task = adapter.build_task_from_event(
event_name,
"delivery-{}".format(event_name),
payload,
{
"enabled_triggers": [enabled_trigger],
"bot_usernames": ["coven-cody[bot]"],
"familiar": familiar,
"publication": {"mode": "record_only"},
},
)

self.assertEqual(task["state"], "queued")
self.assertEqual(task["trigger"], expected_trigger)

def test_labeled_issue_uses_webhook_trigger_name_policy(self):
adapter = load_adapter()

task = adapter.build_task_from_event(
"issues",
"delivery-issue-labeled",
{
"action": "labeled",
"installation": {"id": 123456},
"repository": {
"id": 987654321,
"full_name": "OpenCoven/example",
"clone_url": "https://github.com/OpenCoven/example.git",
"default_branch": "main",
},
"issue": {
"number": 44,
"title": "Fix it",
"body": "Please fix it.",
"labels": [{"name": "coven:fix"}],
},
},
{
"enabled_triggers": ["issues.labeled"],
"trigger_labels": ["coven:fix"],
"bot_usernames": ["coven-cody[bot]"],
"familiar": {
"id": "cody",
"display_name": "Cody",
"model": "openai/gpt-5.5",
"skills": [],
},
"publication": {"mode": "record_only"},
},
)

self.assertEqual(task["state"], "queued")
self.assertEqual(task["trigger"], "issue_assigned")
self.assertEqual(task["task"]["kind"], "fix_issue")

def test_prepare_review_context_rejects_stale_pr_head_evidence(self):
adapter = load_adapter()

Expand Down
Loading