Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
a3f4c83
ci: materialize focused review-agent mention router
seonghobae Aug 5, 2026
0cdc975
chore(automation): remove transient mention-router materializer
seonghobae Aug 5, 2026
e8adc56
test(automation): preserve trusted mention-router contracts
seonghobae Aug 5, 2026
39afd62
test(automation): preserve organization mention sweep contracts
seonghobae Aug 5, 2026
57a6b9b
test(automation): preserve least-privilege mention workflow contract
seonghobae Aug 5, 2026
6c5038c
feat(automation): add trusted review-agent mention router
seonghobae Aug 5, 2026
fab2bf9
feat(automation): add bounded organization mention sweep
seonghobae Aug 5, 2026
1867cc8
test(automation): pin mention-router runner and checkout source
seonghobae Aug 5, 2026
8cd9be3
feat(automation): add trusted review-agent mention workflow
seonghobae Aug 5, 2026
7e53010
ci(automation): enforce mention-router quality gates
seonghobae Aug 5, 2026
f6aaa6e
docs(automation): document review-agent mention routing
seonghobae Aug 5, 2026
580909f
docs(changelog): record trusted mention-router slice
seonghobae Aug 5, 2026
b0d2d11
docs(automation): define mention-router activation gate
seonghobae Aug 5, 2026
b67b99e
test(automation): isolate CLI environment contract
seonghobae Aug 5, 2026
95f5e54
test(automation): require path-based coverage measurement
seonghobae Aug 5, 2026
cf4400f
ci(automation): measure exact mention-router script paths
seonghobae Aug 5, 2026
588ea68
test(agent-router): require per-agent durable dispatch idempotency
seonghobae Aug 5, 2026
ee1e33e
test(agent-router): require downstream exact-key idempotency
seonghobae Aug 5, 2026
cb08d05
test(agent-router): separate workflow trigger and pytest contracts
seonghobae Aug 5, 2026
6008bac
test(agent-router): execute durable idempotency contracts
seonghobae Aug 5, 2026
d7c2335
fix(agent-router): deduplicate exact per-agent dispatches
seonghobae Aug 5, 2026
109a5fa
fix(agent-router): accept empty workflow-run inventory
seonghobae Aug 5, 2026
9e11bb3
test(agent-router): model durable run inventory reads
seonghobae Aug 5, 2026
236fd71
fix(agent-router): grant durable workflow-run read access
seonghobae Aug 5, 2026
78af2e0
test(agent-router): require durable dispatch wrappers
seonghobae Aug 5, 2026
f0522c4
feat(agent-router): add durable Noema dispatch wrapper
seonghobae Aug 5, 2026
88b5960
feat(agent-router): add durable OpenCode dispatch wrapper
seonghobae Aug 5, 2026
d4caec6
feat(agent-router): route through durable dispatch wrappers
seonghobae Aug 5, 2026
68877ff
test(agent-router): expect durable wrapper events
seonghobae Aug 5, 2026
ec201df
test(agent-router): cover wrapper event idempotency
seonghobae Aug 5, 2026
c1171d7
test(agent-router): reject unsupported Bash lookahead
seonghobae Aug 5, 2026
316246c
fix(agent-router): use portable Bash base validation
seonghobae Aug 5, 2026
d69a767
ci(agent-router): track durable dispatch wrappers
seonghobae Aug 5, 2026
7a5db21
test(agent-router): make central run ledger authoritative
seonghobae Aug 5, 2026
75df0cd
ci(agent-router): execute receipt authority contract
seonghobae Aug 5, 2026
d3afa23
test(agent-router): make production paths receipt-independent
seonghobae Aug 5, 2026
f384e2e
fix(agent-router): remove local receipt authority
seonghobae Aug 5, 2026
7689c3a
fix(agent-router): make sweep receipt-independent
seonghobae Aug 5, 2026
b86250d
test: prevent no-op sweep requests from exhausting budget
seonghobae Aug 5, 2026
22e4c18
test: assert job permissions independently
seonghobae Aug 5, 2026
1fd0119
test: accept wildcard quality-gate path coverage
seonghobae Aug 5, 2026
9056842
fix: preserve sweep capacity for newly queued work
seonghobae Aug 5, 2026
9427718
test: cover durable run-ledger failure boundaries
seonghobae Aug 5, 2026
ff02c87
docs: record durable dispatch ledger and permission boundary
seonghobae Aug 5, 2026
678be8b
fix(automation): remove branch-selected mention sweep dispatch
seonghobae Aug 6, 2026
c1e28b2
test(automation): prohibit branch-selected mention sweep dispatch
seonghobae Aug 6, 2026
9027938
docs(automation): document protected mention sweep trigger
seonghobae Aug 6, 2026
8aa7101
fix(automation): preserve app-token fallback semantics
seonghobae Aug 6, 2026
e0f2457
test(ci): cover scheduler package import fallback
seonghobae Aug 6, 2026
84bb106
test(automation): require payload-bound agent invocation keys
seonghobae Aug 6, 2026
ef456e3
ci: materialize payload-bound review-agent invocations
seonghobae Aug 6, 2026
af1b35c
ci: add bounded PR 787 payload-binding materializer
seonghobae Aug 6, 2026
b4105d9
ci(pr787): trigger payload-bound invocation repair
seonghobae Aug 6, 2026
b731c29
ci: trigger bounded PR 787 payload-binding repair
seonghobae Aug 6, 2026
521ce30
ci(pr787): stage validated payload-binding repair
seonghobae Aug 6, 2026
eb73298
ci(pr787): run payload-bound repair v2
seonghobae Aug 6, 2026
4427fe1
ci: retrigger verified PR 787 payload-binding repair
seonghobae Aug 6, 2026
550a869
ci(pr787): build verified payload candidate
seonghobae Aug 6, 2026
53c5dc6
ci(pr787): upload verified payload candidate
seonghobae Aug 6, 2026
55152c6
ci(pr787): export verified reviewed files
seonghobae Aug 6, 2026
306a082
ci(pr787): export reviewed workflow files
seonghobae Aug 6, 2026
5605489
fix(ci): materialize complete PR 787 payload binding
seonghobae Aug 6, 2026
722c942
fix(automation): document payload-bound invocation keys
seonghobae Aug 6, 2026
5313ea7
ci(automation): repair Noema invocation payload binding
seonghobae Aug 6, 2026
51e6ec4
ci(pr787): repair payload-bound invocation keys
seonghobae Aug 6, 2026
7e85114
ci(pr787): finalize payload-bound review dispatch
seonghobae Aug 6, 2026
7fa3fa2
chore(ci): remove PR 787 repair trigger
seonghobae Aug 6, 2026
438dfc6
chore(ci): remove PR 787 export repair workflow
seonghobae Aug 6, 2026
0c72ab2
chore(ci): remove PR 787 workflow export helper
seonghobae Aug 6, 2026
874ecf7
chore(ci): remove PR 787 finalizer workflow
seonghobae Aug 6, 2026
97a14bc
chore(ci): remove PR 787 Noema repair workflow
seonghobae Aug 6, 2026
d47359b
chore(ci): remove PR 787 payload push workflow
seonghobae Aug 6, 2026
b8b5bb2
chore(ci): remove PR 787 invocation repair workflow
seonghobae Aug 6, 2026
ecb93aa
ci(pr787): run payload-binding product finalizer
seonghobae Aug 6, 2026
a7b1bee
chore(ci): remove PR 787 v2 repair workflow
seonghobae Aug 6, 2026
f1024b3
chore(ci): remove PR 787 candidate repair workflow
seonghobae Aug 6, 2026
2002f9b
chore(ci): remove PR 787 digest repair workflow
seonghobae Aug 6, 2026
660cef3
chore(ci): remove PR 787 payload upload workflow
seonghobae Aug 6, 2026
0c88eee
ci(pr787): retrigger payload-binding finalizer
seonghobae Aug 6, 2026
c64b98b
chore(ci): remove PR 787 patch helper
seonghobae Aug 6, 2026
adebafa
chore(ci): remove PR 787 one-shot repair helper
seonghobae Aug 6, 2026
f62dfb1
chore(ci): remove PR 787 payload finalizer
seonghobae Aug 6, 2026
3bef8ad
fix(automation): verify payload-bound invocation keys
seonghobae Aug 6, 2026
85c5091
ci(pr787): reconcile Noema payload binding
seonghobae Aug 6, 2026
92c83f4
chore(ci): remove PR 787 payload reconcile workflow
seonghobae Aug 6, 2026
b2bd9d4
ci(pr787): finalize payload binding and remove repair artifacts
seonghobae Aug 6, 2026
04d715f
ci: remove accidental placeholder issues
seonghobae Aug 6, 2026
fa71834
chore: remove placeholder cleanup workflow
github-actions[bot] Aug 6, 2026
41e6598
ci(pr787): finalize repaired payload tree v2
seonghobae Aug 6, 2026
1cf29e5
fix(automation): bind invocation keys to complete payloads
github-actions[bot] Aug 6, 2026
712dc72
ci: clean exact accidental placeholder issues v2
seonghobae Aug 6, 2026
cb1733a
ci(pr787): finalize durable payload tree v3
seonghobae Aug 6, 2026
0cec3e0
fix(automation): bind invocation keys to complete payloads
github-actions[bot] Aug 6, 2026
6e85bd6
test(automation): prove rejected mentions are mutation-free
seonghobae Aug 6, 2026
3ea8586
ci(automation): execute complete exact-range quality gate
seonghobae Aug 6, 2026
26c859a
chore(automation): stage deterministic PR 787 review repairs
seonghobae Aug 6, 2026
692147f
ci(automation): run verified final PR 787 repair
seonghobae Aug 6, 2026
22bf2d4
ci(automation): verify transient writer removal correctly
seonghobae Aug 6, 2026
49f381c
ci(automation): make PR 787 finalizer observable
seonghobae Aug 6, 2026
b9ed228
fix(automation): repair transient transformer matching
seonghobae Aug 6, 2026
89fd7d0
ci(automation): repair and authenticate final state writer
seonghobae Aug 6, 2026
6cb7b20
fix(automation): tolerate typed function end markers
seonghobae Aug 6, 2026
eaeadd9
fix(automation): match indented workflow blocks
seonghobae Aug 6, 2026
7ddbc7a
fix(automation): preserve generated test escape sequences
seonghobae Aug 6, 2026
440143b
fix(automation): preserve generated router newline escapes
seonghobae Aug 6, 2026
576141e
test(automation): close remaining router coverage branches
seonghobae Aug 6, 2026
384b1b8
ci(automation): expose final uncovered sweep branch
seonghobae Aug 6, 2026
50b039f
test(automation): cover empty pagination termination
seonghobae Aug 6, 2026
107617f
ci(automation): split verified runtime and workflow publication
seonghobae Aug 6, 2026
7ba61cb
fix(automation): close mention-router runtime findings
github-actions[bot] Aug 6, 2026
2744718
ci(automation): remove nonexistent router path trigger
seonghobae Aug 6, 2026
100203d
fix(automation): make Noema wrapper leader election resilient
seonghobae Aug 6, 2026
c24721d
fix(automation): make OpenCode wrapper leader election resilient
seonghobae Aug 6, 2026
e028648
fix(automation): harden sweep token exchange and handoff
seonghobae Aug 6, 2026
f572094
chore(automation): remove transient PR 787 finalizer
seonghobae Aug 6, 2026
11a6a8d
test(automation): specify exact-name artifact ledger
seonghobae Aug 6, 2026
613718e
fix(automation): use exact-name artifact dispatch ledger
seonghobae Aug 6, 2026
7e4ba8c
fix(automation): share exact artifact ledger cache in sweep
seonghobae Aug 6, 2026
8846c52
fix(automation): claim Noema dispatches with exact artifacts
seonghobae Aug 6, 2026
bada44c
fix(automation): claim OpenCode dispatches with exact artifacts
seonghobae Aug 6, 2026
ff37f98
test(automation): migrate idempotency coverage to artifacts
seonghobae Aug 6, 2026
0e95e15
test(automation): migrate review regressions to artifacts
seonghobae Aug 6, 2026
f732cdb
test(automation): assert artifact-first wrapper claims
seonghobae Aug 6, 2026
1282c01
test(automation): assert shared artifact cache in sweep
seonghobae Aug 6, 2026
76d79d7
docs(automation): document exact artifact ledger
seonghobae Aug 6, 2026
d2c7d41
test(automation): return artifact inventories in router fakes
seonghobae Aug 6, 2026
29ff8b5
style(automation): annotate intentional failure isolation
seonghobae Aug 6, 2026
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
190 changes: 190 additions & 0 deletions .github/workflows/agent-mention-noema-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: Agent Mention Noema Dispatch
run-name: >-
Agent Mention Noema ${{ github.event.client_payload.target_repository }}#${{
github.event.client_payload.pr_number }} [cwl-agent-invocation:${{
github.event.client_payload.agent_invocation_key }}]

on:
repository_dispatch:
types: [agent-mention-noema]

concurrency:
group: agent-mention-noema-${{ github.event.client_payload.agent_invocation_key || github.run_id }}
cancel-in-progress: false
queue: max

permissions:
contents: read

jobs:
validate-and-forward:
if: github.repository == 'ContextualWisdomLab/.github'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: read
contents: write
env:
GH_TOKEN: ${{ github.token }}
REQUESTED_AGENT: "cwl-noema-review"
PAYLOAD_AGENT: ${{ github.event.client_payload.requested_agent || '' }}
INVOCATION_KEY: ${{ github.event.client_payload.agent_invocation_key || '' }}
TARGET_REPOSITORY: ${{ github.event.client_payload.target_repository || '' }}
PR_NUMBER: ${{ github.event.client_payload.pr_number || '' }}
PR_HEAD_SHA: ${{ github.event.client_payload.pr_head_sha || '' }}
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 || '' }}
steps:
- name: Validate exact invocation payload
run: |
set -euo pipefail
if [ "$PAYLOAD_AGENT" != "$REQUESTED_AGENT" ] ||
! [[ "$INVOCATION_KEY" =~ ^[0-9a-f]{64}$ ]] ||
! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$PR_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] ||
! [[ "$BASE_BRANCH" =~ ^[A-Za-z0-9._/-]+$ ]] ||
[[ "$BASE_BRANCH" == -* ]] ||
! [[ "$SOURCE_COMMENT_ID" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$REQUESTED_BY" =~ ^[A-Za-z0-9-]+$ ]]; then
echo "::error::Rejected malformed or mismatched Noema agent invocation payload."
exit 1
fi

python3 - <<'PYTHON'
import hashlib
import hmac
import json
import os

canonical = json.dumps(
{
"actor": os.environ["REQUESTED_BY"],
"agent": os.environ["REQUESTED_AGENT"],
"base_branch": os.environ["BASE_BRANCH"],
"comment_id": int(os.environ["SOURCE_COMMENT_ID"]),
"head_sha": os.environ["PR_HEAD_SHA"],
"pr_number": int(os.environ["PR_NUMBER"]),
"repository": os.environ["TARGET_REPOSITORY"],
},
ensure_ascii=True,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
expected = hashlib.sha256(canonical).hexdigest()
if not hmac.compare_digest(expected, os.environ["INVOCATION_KEY"]):
raise SystemExit("invocation key does not match canonical payload")
PYTHON

- name: Inspect exact-name Actions artifact ledger
id: ledger
run: |
set -euo pipefail
LEDGER_ARTIFACT_NAME="cwl-agent-invocation-${INVOCATION_KEY}"
export LEDGER_ARTIFACT_NAME
echo "LEDGER_ARTIFACT_NAME=$LEDGER_ARTIFACT_NAME" >>"$GITHUB_ENV"
response_file="${RUNNER_TEMP}/agent-mention-artifacts.json"
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts" \
-X GET \
-f "name=${LEDGER_ARTIFACT_NAME}" \
-f "per_page=100" >"$response_file"
python3 - "$response_file" <<'PYTHON'
import json
import os
from pathlib import Path
import sys

response_path = Path(sys.argv[1])
payload = json.loads(response_path.read_text(encoding="utf-8"))
expected_name = os.environ["LEDGER_ARTIFACT_NAME"]
if not isinstance(payload, dict):
raise SystemExit("artifact response must be an object")
total_count = payload.get("total_count")
artifacts = payload.get("artifacts")
if type(total_count) is not int or total_count < 0:
raise SystemExit("artifact response has an invalid total_count")
if not isinstance(artifacts, list):
raise SystemExit("artifact response has an invalid artifacts collection")
if total_count != len(artifacts):
raise SystemExit("artifact response is truncated or inconsistent")
live = False
for artifact in artifacts:
if not isinstance(artifact, dict):
raise SystemExit("artifact response contains a non-object record")
artifact_id = artifact.get("id")
name = artifact.get("name")
expired = artifact.get("expired")
if type(artifact_id) is not int or artifact_id < 1:
raise SystemExit("artifact response contains an invalid artifact id")
if not isinstance(name, str) or name != expected_name:
raise SystemExit("artifact response contains a mismatched artifact name")
if type(expired) is not bool:
raise SystemExit("artifact response contains an invalid expired flag")
live = live or not expired

output_path = Path(os.environ["GITHUB_OUTPUT"])
if live:
with output_path.open("a", encoding="utf-8") as handle:
handle.write("claim=false\n")
raise SystemExit(0)

claim_dir = Path(os.environ["RUNNER_TEMP"]) / "cwl-agent-invocation-ledger"
claim_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
claim = {
"actor": os.environ["REQUESTED_BY"],
"agent": os.environ["REQUESTED_AGENT"],
"base_branch": os.environ["BASE_BRANCH"],
"comment_id": int(os.environ["SOURCE_COMMENT_ID"]),
"head_sha": os.environ["PR_HEAD_SHA"],
"invocation_key": os.environ["INVOCATION_KEY"],
"pr_number": int(os.environ["PR_NUMBER"]),
"repository": os.environ["TARGET_REPOSITORY"],
}
(claim_dir / "claim.json").write_text(
json.dumps(claim, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
with output_path.open("a", encoding="utf-8") as handle:
handle.write("claim=true\n")
PYTHON

- name: Claim exact invocation in the durable artifact ledger
if: steps.ledger.outputs.claim == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cwl-agent-invocation-${{ env.INVOCATION_KEY }}
path: ${{ runner.temp }}/cwl-agent-invocation-ledger/claim.json
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false

- name: Forward once to the authoritative Noema workflow
if: steps.ledger.outputs.claim == 'true'
run: |
set -euo pipefail
jq -n \
--arg target_repository "$TARGET_REPOSITORY" \
--argjson pr_number "$PR_NUMBER" \
--arg pr_head_sha "$PR_HEAD_SHA" \
--arg base_branch "$BASE_BRANCH" \
--arg requested_agent "$REQUESTED_AGENT" \
--arg agent_invocation_key "$INVOCATION_KEY" \
--arg requested_by "$REQUESTED_BY" \
--argjson source_comment_id "$SOURCE_COMMENT_ID" \
'{
event_type: "noema-review",
client_payload: {
target_repository: $target_repository,
pr_number: $pr_number,
pr_head_sha: $pr_head_sha,
base_branch: $base_branch,
requested_agent: $requested_agent,
agent_invocation_key: $agent_invocation_key,
requested_by: $requested_by,
source_comment_id: $source_comment_id
}
}' \
| gh api "repos/${GITHUB_REPOSITORY}/dispatches" -X POST --input -
205 changes: 205 additions & 0 deletions .github/workflows/agent-mention-opencode-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
name: Agent Mention OpenCode Dispatch
run-name: >-
Agent Mention OpenCode ${{ github.event.client_payload.target_repository }}#${{
github.event.client_payload.pr_number }} [cwl-agent-invocation:${{
github.event.client_payload.agent_invocation_key }}]

on:
repository_dispatch:
types: [agent-mention-opencode]

concurrency:
group: agent-mention-opencode-${{ github.event.client_payload.agent_invocation_key || github.run_id }}
cancel-in-progress: false
queue: max

permissions:
contents: read

jobs:
validate-and-forward:
if: github.repository == 'ContextualWisdomLab/.github'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: read
contents: write
env:
GH_TOKEN: ${{ github.token }}
REQUESTED_AGENT: "opencode-agent"
PAYLOAD_AGENT: ${{ github.event.client_payload.requested_agent || '' }}
INVOCATION_KEY: ${{ github.event.client_payload.agent_invocation_key || '' }}
TARGET_REPOSITORY: ${{ github.event.client_payload.target_repository || '' }}
PR_NUMBER: ${{ github.event.client_payload.pr_number || '' }}
PR_HEAD_SHA: ${{ github.event.client_payload.pr_head_sha || '' }}
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: ${{ github.event.client_payload.trigger_reviews }}
REVIEW_DISPATCH_LIMIT: ${{ github.event.client_payload.review_dispatch_limit || '' }}
ENABLE_AUTO_MERGE: ${{ github.event.client_payload.enable_auto_merge }}
UPDATE_BRANCHES: ${{ github.event.client_payload.update_branches }}
MERGE_MODE: ${{ github.event.client_payload.merge_mode || '' }}
steps:
- name: Validate exact invocation payload
run: |
set -euo pipefail
if [ "$PAYLOAD_AGENT" != "$REQUESTED_AGENT" ] ||
! [[ "$INVOCATION_KEY" =~ ^[0-9a-f]{64}$ ]] ||
! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$PR_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] ||
! [[ "$BASE_BRANCH" =~ ^[A-Za-z0-9._/-]+$ ]] ||
[[ "$BASE_BRANCH" == -* ]] ||
! [[ "$SOURCE_COMMENT_ID" =~ ^[1-9][0-9]*$ ]] ||
[ "$TRIGGER_REVIEWS" != "true" ] ||
[ "$REVIEW_DISPATCH_LIMIT" != "1" ] ||
[ "$ENABLE_AUTO_MERGE" != "false" ] ||
[ "$UPDATE_BRANCHES" != "false" ] ||
[ "$MERGE_MODE" != "disabled" ] ||
! [[ "$REQUESTED_BY" =~ ^[A-Za-z0-9-]+$ ]]; then
echo "::error::Rejected malformed or mismatched OpenCode agent invocation payload."
exit 1
fi

python3 - <<'PYTHON'
import hashlib
import hmac
import json
import os

canonical = json.dumps(
{
"actor": os.environ["REQUESTED_BY"],
"agent": os.environ["REQUESTED_AGENT"],
"base_branch": os.environ["BASE_BRANCH"],
"comment_id": int(os.environ["SOURCE_COMMENT_ID"]),
"head_sha": os.environ["PR_HEAD_SHA"],
"pr_number": int(os.environ["PR_NUMBER"]),
"repository": os.environ["TARGET_REPOSITORY"],
},
ensure_ascii=True,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
expected = hashlib.sha256(canonical).hexdigest()
if not hmac.compare_digest(expected, os.environ["INVOCATION_KEY"]):
raise SystemExit("invocation key does not match canonical payload")
PYTHON

- name: Inspect exact-name Actions artifact ledger
id: ledger
run: |
set -euo pipefail
LEDGER_ARTIFACT_NAME="cwl-agent-invocation-${INVOCATION_KEY}"
export LEDGER_ARTIFACT_NAME
echo "LEDGER_ARTIFACT_NAME=$LEDGER_ARTIFACT_NAME" >>"$GITHUB_ENV"
response_file="${RUNNER_TEMP}/agent-mention-artifacts.json"
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts" \
-X GET \
-f "name=${LEDGER_ARTIFACT_NAME}" \
-f "per_page=100" >"$response_file"
python3 - "$response_file" <<'PYTHON'
import json
import os
from pathlib import Path
import sys

response_path = Path(sys.argv[1])
payload = json.loads(response_path.read_text(encoding="utf-8"))
expected_name = os.environ["LEDGER_ARTIFACT_NAME"]
if not isinstance(payload, dict):
raise SystemExit("artifact response must be an object")
total_count = payload.get("total_count")
artifacts = payload.get("artifacts")
if type(total_count) is not int or total_count < 0:
raise SystemExit("artifact response has an invalid total_count")
if not isinstance(artifacts, list):
raise SystemExit("artifact response has an invalid artifacts collection")
if total_count != len(artifacts):
raise SystemExit("artifact response is truncated or inconsistent")
live = False
for artifact in artifacts:
if not isinstance(artifact, dict):
raise SystemExit("artifact response contains a non-object record")
artifact_id = artifact.get("id")
name = artifact.get("name")
expired = artifact.get("expired")
if type(artifact_id) is not int or artifact_id < 1:
raise SystemExit("artifact response contains an invalid artifact id")
if not isinstance(name, str) or name != expected_name:
raise SystemExit("artifact response contains a mismatched artifact name")
if type(expired) is not bool:
raise SystemExit("artifact response contains an invalid expired flag")
live = live or not expired

output_path = Path(os.environ["GITHUB_OUTPUT"])
if live:
with output_path.open("a", encoding="utf-8") as handle:
handle.write("claim=false\n")
raise SystemExit(0)

claim_dir = Path(os.environ["RUNNER_TEMP"]) / "cwl-agent-invocation-ledger"
claim_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
claim = {
"actor": os.environ["REQUESTED_BY"],
"agent": os.environ["REQUESTED_AGENT"],
"base_branch": os.environ["BASE_BRANCH"],
"comment_id": int(os.environ["SOURCE_COMMENT_ID"]),
"head_sha": os.environ["PR_HEAD_SHA"],
"invocation_key": os.environ["INVOCATION_KEY"],
"pr_number": int(os.environ["PR_NUMBER"]),
"repository": os.environ["TARGET_REPOSITORY"],
}
(claim_dir / "claim.json").write_text(
json.dumps(claim, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
with output_path.open("a", encoding="utf-8") as handle:
handle.write("claim=true\n")
PYTHON

- name: Claim exact invocation in the durable artifact ledger
if: steps.ledger.outputs.claim == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cwl-agent-invocation-${{ env.INVOCATION_KEY }}
path: ${{ runner.temp }}/cwl-agent-invocation-ledger/claim.json
if-no-files-found: error
retention-days: 30
compression-level: 0
overwrite: false
include-hidden-files: false

- name: Forward once to the authoritative review-only scheduler
if: steps.ledger.outputs.claim == 'true'
run: |
set -euo pipefail
jq -n \
--arg target_repository "$TARGET_REPOSITORY" \
--argjson pr_number "$PR_NUMBER" \
--arg pr_head_sha "$PR_HEAD_SHA" \
--arg base_branch "$BASE_BRANCH" \
--arg requested_agent "$REQUESTED_AGENT" \
--arg agent_invocation_key "$INVOCATION_KEY" \
--arg requested_by "$REQUESTED_BY" \
--argjson source_comment_id "$SOURCE_COMMENT_ID" \
'{
event_type: "merge-scheduler",
client_payload: {
target_repository: $target_repository,
pr_number: $pr_number,
pr_head_sha: $pr_head_sha,
base_branch: $base_branch,
trigger_reviews: true,
review_dispatch_limit: "1",
enable_auto_merge: false,
update_branches: false,
merge_mode: "disabled",
requested_agent: $requested_agent,
agent_invocation_key: $agent_invocation_key,
requested_by: $requested_by,
source_comment_id: $source_comment_id
}
}' \
| gh api "repos/${GITHUB_REPOSITORY}/dispatches" -X POST --input -
Loading
Loading