From 416b23de741747b438a1b7d6dcd193ca5a792caf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 21:14:13 +0900 Subject: [PATCH 1/2] test(agent-mention): reproduce repository dispatch payload cap --- .../agent-mention-opencode-dispatch.yml | 23 +++--- scripts/ci/agent_mention_router.py | 0 ..._agent_mention_complete_payload_binding.py | 77 ++++++++++++++++--- tests/test_agent_mention_router.py | 20 ++++- 4 files changed, 97 insertions(+), 23 deletions(-) mode change 100644 => 100755 scripts/ci/agent_mention_router.py diff --git a/.github/workflows/agent-mention-opencode-dispatch.yml b/.github/workflows/agent-mention-opencode-dispatch.yml index 02a3f6f08..4aef0b086 100644 --- a/.github/workflows/agent-mention-opencode-dispatch.yml +++ b/.github/workflows/agent-mention-opencode-dispatch.yml @@ -11,7 +11,6 @@ on: concurrency: group: agent-mention-opencode-${{ github.event.client_payload.agent_invocation_key || github.run_id }} cancel-in-progress: false - queue: max permissions: contents: read @@ -36,11 +35,15 @@ jobs: BASE_BRANCH: ${{ github.event.client_payload.base_branch || '' }} REQUESTED_BY: ${{ github.event.client_payload.requested_by || '' }} SOURCE_COMMENT_ID: ${{ github.event.client_payload.source_comment_id || '' }} - TRIGGER_REVIEWS: "true" - REVIEW_DISPATCH_LIMIT: "1" - ENABLE_AUTO_MERGE: "false" - UPDATE_BRANCHES: "false" - MERGE_MODE: "disabled" + # The router intentionally omits these immutable review-only controls so + # the repository_dispatch payload stays under GitHub's 10-property cap. + # The trusted wrapper reconstructs the canonical values before validating + # the invocation key, so transport minimization cannot weaken claim binding. + TRIGGER_REVIEWS: ${{ github.event.client_payload.trigger_reviews || 'true' }} + REVIEW_DISPATCH_LIMIT: ${{ github.event.client_payload.review_dispatch_limit || '1' }} + ENABLE_AUTO_MERGE: ${{ github.event.client_payload.enable_auto_merge || 'false' }} + UPDATE_BRANCHES: ${{ github.event.client_payload.update_branches || 'false' }} + MERGE_MODE: ${{ github.event.client_payload.merge_mode || 'disabled' }} steps: - name: Validate exact invocation payload run: | @@ -195,8 +198,6 @@ jobs: --arg pr_head_sha "$PR_HEAD_SHA" \ --arg pr_base_sha "$PR_BASE_SHA" \ --arg base_branch "$BASE_BRANCH" \ - --arg agent_invocation_key "$INVOCATION_KEY" \ - --argjson source_comment_id "$SOURCE_COMMENT_ID" \ '{ event_type: "merge-scheduler", client_payload: { @@ -205,11 +206,11 @@ jobs: pr_head_sha: $pr_head_sha, pr_base_sha: $pr_base_sha, base_branch: $base_branch, + trigger_reviews: true, + review_dispatch_limit: "1", enable_auto_merge: false, update_branches: false, - merge_mode: "disabled", - agent_invocation_key: $agent_invocation_key, - source_comment_id: $source_comment_id + merge_mode: "disabled" } }' \ | gh api "repos/${GITHUB_REPOSITORY}/dispatches" -X POST --input - diff --git a/scripts/ci/agent_mention_router.py b/scripts/ci/agent_mention_router.py old mode 100644 new mode 100755 diff --git a/tests/test_agent_mention_complete_payload_binding.py b/tests/test_agent_mention_complete_payload_binding.py index c07025407..fae5d9874 100644 --- a/tests/test_agent_mention_complete_payload_binding.py +++ b/tests/test_agent_mention_complete_payload_binding.py @@ -162,17 +162,6 @@ def test_wrappers_recompute_complete_claim_before_ledger_access() -> None: assert "--arg pr_base_sha \"$PR_BASE_SHA\"" in workflow assert "pr_base_sha: $pr_base_sha" in workflow - assert "github.event.client_payload.trigger_reviews" not in opencode - assert "github.event.client_payload.review_dispatch_limit" not in opencode - assert "github.event.client_payload.enable_auto_merge" not in opencode - assert "github.event.client_payload.update_branches" not in opencode - assert "github.event.client_payload.merge_mode" not in opencode - assert 'TRIGGER_REVIEWS: "true"' in opencode - assert 'REVIEW_DISPATCH_LIMIT: "1"' in opencode - assert 'ENABLE_AUTO_MERGE: "false"' in opencode - assert 'UPDATE_BRANCHES: "false"' in opencode - assert 'MERGE_MODE: "disabled"' in opencode - for field in ( '"trigger_reviews": os.environ["TRIGGER_REVIEWS"] == "true"', '"review_dispatch_limit": os.environ["REVIEW_DISPATCH_LIMIT"]', @@ -193,6 +182,72 @@ def test_wrappers_recompute_complete_claim_before_ledger_access() -> None: assert opencode.count(field) >= 2 +def test_repository_dispatch_payloads_stay_within_github_property_limit() -> None: + """Keep both OpenCode dispatch hops at GitHub's ten-property API boundary.""" + + router = _load_router() + request = router.parse_event(_event()) + assert request is not None + noema_payload = router.noema_payload(request)["client_payload"] + opencode_payload = router.opencode_payload(request)["client_payload"] + + assert len(noema_payload) <= 10 + assert set(opencode_payload) == { + "target_repository", + "pr_number", + "pr_head_sha", + "pr_base_sha", + "base_branch", + "requested_agent", + "agent_invocation_key", + "requested_by", + "source_comment_id", + } + assert len(opencode_payload) <= 10 + + workflow = OPENCODE_WORKFLOW.read_text(encoding="utf-8") + for default in ( + "github.event.client_payload.trigger_reviews || 'true'", + "github.event.client_payload.review_dispatch_limit || '1'", + "github.event.client_payload.enable_auto_merge || 'false'", + "github.event.client_payload.update_branches || 'false'", + "github.event.client_payload.merge_mode || 'disabled'", + ): + assert default in workflow + + forward = workflow.split( + " - name: Forward once to the authoritative review-only scheduler\n", 1 + )[1] + payload_literal = forward.split("client_payload: {\n", 1)[1].split( + "\n }\n }'", 1 + )[0] + forwarded_keys = { + line.strip().split(":", 1)[0] + for line in payload_literal.splitlines() + if ":" in line + } + assert forwarded_keys == { + "target_repository", + "pr_number", + "pr_head_sha", + "pr_base_sha", + "base_branch", + "trigger_reviews", + "review_dispatch_limit", + "enable_auto_merge", + "update_branches", + "merge_mode", + } + assert len(forwarded_keys) <= 10 + for wrapper_identity in ( + "requested_agent", + "agent_invocation_key", + "requested_by", + "source_comment_id", + ): + assert f"{wrapper_identity}:" not in payload_literal + + def test_no_pr_specific_writer_workflow_remains() -> None: """Complete binding is implemented in canonical files, never a branch writer.""" diff --git a/tests/test_agent_mention_router.py b/tests/test_agent_mention_router.py index 874a79e4f..2590e0a1c 100644 --- a/tests/test_agent_mention_router.py +++ b/tests/test_agent_mention_router.py @@ -201,7 +201,7 @@ def test_receipt_and_allowlist_helpers() -> None: def test_eligible_agents_and_payloads() -> None: - """Eligibility and event bodies preserve the bounded review contract.""" + """Eligibility and wrapper transport preserve the bounded review contract.""" module = load_module() request = module.parse_event(event("@cwl-noema-review @opencode-agent")) @@ -218,14 +218,31 @@ def test_eligible_agents_and_payloads() -> None: assert noema["event_type"] == "agent-mention-noema" assert noema["client_payload"]["pr_head_sha"] == "a" * 40 assert noema["client_payload"]["pr_base_sha"] == "b" * 40 + opencode = module.opencode_payload(request) assert opencode["event_type"] == "agent-mention-opencode" + assert set(opencode["client_payload"]) == { + "target_repository", + "pr_number", + "pr_head_sha", + "pr_base_sha", + "base_branch", + "requested_agent", + "agent_invocation_key", + "requested_by", + "source_comment_id", + } + assert len(opencode["client_payload"]) == 9 assert opencode["client_payload"]["base_branch"] == "develop" assert opencode["client_payload"]["pr_base_sha"] == "b" * 40 assert "merge_mode" not in opencode["client_payload"] assert "enable_auto_merge" not in opencode["client_payload"] assert "update_branches" not in opencode["client_payload"] claim = module.agent_invocation_claim(request, "opencode-agent") + + claim = module.agent_invocation_claim(request, "opencode-agent") + assert claim["trigger_reviews"] is True + assert claim["review_dispatch_limit"] == "1" assert claim["merge_mode"] == "disabled" assert claim["enable_auto_merge"] is False assert claim["update_branches"] is False @@ -255,6 +272,7 @@ def test_dispatch_uses_central_events_and_acknowledges() -> None: args[0] == "repos/ContextualWisdomLab/.github/dispatches" for args, _ in dispatches ) + assert len(dispatches[1][1]["client_payload"]) == 9 assert target.calls[0][1] == {"content": "eyes"} assert "cwl-agent-mention-receipt:91" in target.calls[1][1]["body"] assert "exact-name Actions artifacts" in target.calls[1][1]["body"] From 64e4735a773ca7f4d86dca1d12866168595b50f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 20:59:24 +0900 Subject: [PATCH 2/2] fix(ci): bind OpenCode forwarding identity under payload cap --- .../agent-mention-opencode-dispatch.yml | 23 ++++++++-------- ..._agent_mention_complete_payload_binding.py | 27 ++++++++----------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/.github/workflows/agent-mention-opencode-dispatch.yml b/.github/workflows/agent-mention-opencode-dispatch.yml index 4aef0b086..02a3f6f08 100644 --- a/.github/workflows/agent-mention-opencode-dispatch.yml +++ b/.github/workflows/agent-mention-opencode-dispatch.yml @@ -11,6 +11,7 @@ on: concurrency: group: agent-mention-opencode-${{ github.event.client_payload.agent_invocation_key || github.run_id }} cancel-in-progress: false + queue: max permissions: contents: read @@ -35,15 +36,11 @@ jobs: BASE_BRANCH: ${{ github.event.client_payload.base_branch || '' }} REQUESTED_BY: ${{ github.event.client_payload.requested_by || '' }} SOURCE_COMMENT_ID: ${{ github.event.client_payload.source_comment_id || '' }} - # The router intentionally omits these immutable review-only controls so - # the repository_dispatch payload stays under GitHub's 10-property cap. - # The trusted wrapper reconstructs the canonical values before validating - # the invocation key, so transport minimization cannot weaken claim binding. - TRIGGER_REVIEWS: ${{ github.event.client_payload.trigger_reviews || 'true' }} - REVIEW_DISPATCH_LIMIT: ${{ github.event.client_payload.review_dispatch_limit || '1' }} - ENABLE_AUTO_MERGE: ${{ github.event.client_payload.enable_auto_merge || 'false' }} - UPDATE_BRANCHES: ${{ github.event.client_payload.update_branches || 'false' }} - MERGE_MODE: ${{ github.event.client_payload.merge_mode || 'disabled' }} + TRIGGER_REVIEWS: "true" + REVIEW_DISPATCH_LIMIT: "1" + ENABLE_AUTO_MERGE: "false" + UPDATE_BRANCHES: "false" + MERGE_MODE: "disabled" steps: - name: Validate exact invocation payload run: | @@ -198,6 +195,8 @@ jobs: --arg pr_head_sha "$PR_HEAD_SHA" \ --arg pr_base_sha "$PR_BASE_SHA" \ --arg base_branch "$BASE_BRANCH" \ + --arg agent_invocation_key "$INVOCATION_KEY" \ + --argjson source_comment_id "$SOURCE_COMMENT_ID" \ '{ event_type: "merge-scheduler", client_payload: { @@ -206,11 +205,11 @@ jobs: pr_head_sha: $pr_head_sha, pr_base_sha: $pr_base_sha, base_branch: $base_branch, - trigger_reviews: true, - review_dispatch_limit: "1", enable_auto_merge: false, update_branches: false, - merge_mode: "disabled" + merge_mode: "disabled", + agent_invocation_key: $agent_invocation_key, + source_comment_id: $source_comment_id } }' \ | gh api "repos/${GITHUB_REPOSITORY}/dispatches" -X POST --input - diff --git a/tests/test_agent_mention_complete_payload_binding.py b/tests/test_agent_mention_complete_payload_binding.py index fae5d9874..5519e44cc 100644 --- a/tests/test_agent_mention_complete_payload_binding.py +++ b/tests/test_agent_mention_complete_payload_binding.py @@ -206,14 +206,14 @@ def test_repository_dispatch_payloads_stay_within_github_property_limit() -> Non assert len(opencode_payload) <= 10 workflow = OPENCODE_WORKFLOW.read_text(encoding="utf-8") - for default in ( - "github.event.client_payload.trigger_reviews || 'true'", - "github.event.client_payload.review_dispatch_limit || '1'", - "github.event.client_payload.enable_auto_merge || 'false'", - "github.event.client_payload.update_branches || 'false'", - "github.event.client_payload.merge_mode || 'disabled'", + for constant in ( + 'TRIGGER_REVIEWS: "true"', + 'REVIEW_DISPATCH_LIMIT: "1"', + 'ENABLE_AUTO_MERGE: "false"', + 'UPDATE_BRANCHES: "false"', + 'MERGE_MODE: "disabled"', ): - assert default in workflow + assert constant in workflow forward = workflow.split( " - name: Forward once to the authoritative review-only scheduler\n", 1 @@ -232,20 +232,15 @@ def test_repository_dispatch_payloads_stay_within_github_property_limit() -> Non "pr_head_sha", "pr_base_sha", "base_branch", - "trigger_reviews", - "review_dispatch_limit", "enable_auto_merge", "update_branches", "merge_mode", - } - assert len(forwarded_keys) <= 10 - for wrapper_identity in ( - "requested_agent", "agent_invocation_key", - "requested_by", "source_comment_id", - ): - assert f"{wrapper_identity}:" not in payload_literal + } + assert len(forwarded_keys) <= 10 + assert "requested_agent:" not in payload_literal + assert "requested_by:" not in payload_literal def test_no_pr_specific_writer_workflow_remains() -> None: