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
23 changes: 14 additions & 9 deletions .github/workflows/pr-review-fix-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ on:
retry_hours:
description: Minimum hours before redispatching autofix for the same head
required: false
default: "24"
default: "1"
type: string
autofix_workflow:
description: Autofix workflow file to dispatch
Expand All @@ -44,14 +44,16 @@ on:
default: ""
type: string
canonical_ref:
description: Ref of ContextualWisdomLab/.github to use for scheduler code
description: Deprecated compatibility input; accepted and ignored because privileged source is bound to the called workflow SHA
required: false
default: "main"
default: ""
type: string
repository_dispatch:
types: [pr-review-fix-scheduler]
schedule:
- cron: "23 */2 * * *"
# Run away from minute zero, where scheduled GitHub Actions are more likely
# to be delayed, while preserving a bounded one-dispatch-per-run repair loop.
- cron: "23 * * * *"

concurrency:
group: central-pr-review-fix-scheduler-${{ github.event.client_payload.target_repository || inputs.target_repository || vars.PR_REVIEW_FIX_TARGET_REPOSITORY || github.repository }}
Expand Down Expand Up @@ -80,16 +82,19 @@ jobs:
DRY_RUN: ${{ github.event.client_payload.dry_run == true || github.event.client_payload.dry_run == 'true' || inputs.dry_run == true }}
MAX_PRS: ${{ github.event.client_payload.max_prs || inputs.max_prs || '50' }}
MAX_DISPATCHES: ${{ github.event.client_payload.max_dispatches || inputs.max_dispatches || '1' }}
RETRY_HOURS: ${{ github.event.client_payload.retry_hours || inputs.retry_hours || '24' }}
RETRY_HOURS: ${{ github.event.client_payload.retry_hours || inputs.retry_hours || '1' }}
AUTOFIX_WORKFLOW: pr-review-autofix.yml
AUTOFIX_REPOSITORY: ContextualWisdomLab/.github
CANONICAL_REF: main
steps:
- name: Checkout canonical scheduler
- name: Checkout immutable called-workflow source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ContextualWisdomLab/.github
ref: ${{ env.CANONICAL_REF }}
# For a reusable workflow the ordinary github context belongs to the
# caller. The job workflow context identifies the called workflow's
# repository and immutable resolved SHA, preventing a caller input or
# mutable branch from selecting the privileged scheduler code.
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
fetch-depth: 1
persist-credentials: false

Expand Down
3 changes: 2 additions & 1 deletion requirements-strix-ci.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
strix-agent==1.0.4
google-cloud-aiplatform==1.133.0
protobuf<7.0.0
cryptography==49.0.0
aiohttp==3.14.3
cryptography==50.0.0
python-multipart==0.0.32
pyasn1==0.6.4
30 changes: 30 additions & 0 deletions tests/test_hourly_review_fix_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Contract tests for the autonomous hourly review-feedback repair loop."""

from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[1]


def test_review_fix_scheduler_runs_hourly_with_bounded_retry() -> None:
"""Keep feedback repair hourly, off minute zero, and bounded per PR head."""
workflow = (
REPO_ROOT / ".github" / "workflows" / "pr-review-fix-scheduler.yml"
).read_text(encoding="utf-8")

schedule_contract = workflow.split("schedule:", 1)[1].split("concurrency:", 1)[0]
retry_contract = workflow.split("retry_hours:", 1)[1].split(
"autofix_workflow:", 1
)[0]

assert 'cron: "23 * * * *"' in schedule_contract
assert 'cron: "23 */2 * * *"' not in schedule_contract
assert 'default: "1"' in retry_contract
assert (
"RETRY_HOURS: ${{ github.event.client_payload.retry_hours || "
"inputs.retry_hours || '1' }}"
) in workflow
assert (
"MAX_DISPATCHES: ${{ github.event.client_payload.max_dispatches || "
"inputs.max_dispatches || '1' }}"
) in workflow
54 changes: 54 additions & 0 deletions tests/test_pr_review_fix_scheduler_source_pin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Supply-chain contract for the reusable PR-review autofix scheduler."""

from __future__ import annotations

from pathlib import Path


_REPO_ROOT = Path(__file__).resolve().parents[1]
_WORKFLOW = _REPO_ROOT / ".github" / "workflows" / "pr-review-fix-scheduler.yml"


def _workflow_text() -> str:
"""Read the reusable scheduler workflow as UTF-8 text."""
return _WORKFLOW.read_text(encoding="utf-8")


def test_reusable_scheduler_checks_out_the_called_workflow_sha() -> None:
"""Privileged scheduler code comes from the immutable called-workflow revision."""
workflow = _workflow_text()
assert "repository: ${{ job.workflow_repository }}" in workflow
assert "ref: ${{ job.workflow_sha }}" in workflow
assert "persist-credentials: false" in workflow


def test_reusable_scheduler_source_is_not_caller_input_controlled() -> None:
"""No caller-supplied ref or ordinary caller GitHub SHA selects trusted code."""
workflow = _workflow_text()
assert "inputs.canonical_ref" not in workflow
assert "github.event.client_payload.canonical_ref" not in workflow
assert "ref: ${{ env.CANONICAL_REF }}" not in workflow
assert "ref: ${{ github.sha }}" not in workflow


def test_deprecated_canonical_ref_input_is_accepted_but_never_consumed() -> None:
"""Existing callers can upgrade pins without controlling privileged source."""
workflow = _workflow_text()
declaration = workflow.split("canonical_ref:", 1)[1].split(
"repository_dispatch:", 1
)[0]

assert "Deprecated compatibility input" in declaration
assert "ignored" in declaration
assert 'default: ""' in declaration
assert workflow.count("canonical_ref") == 1


def test_reusable_scheduler_retains_least_privilege_and_bounded_dispatch() -> None:
"""Source pinning does not broaden token scope or queue fan-out."""
workflow = _workflow_text()
assert "contents: write" not in workflow
assert "pull-requests: write" not in workflow
assert "MAX_DISPATCHES:" in workflow
assert "RETRY_HOURS:" in workflow
assert "cancel-in-progress: true" in workflow
Loading