Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/issue-ci-required-gates.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"aw_revision": "67af8d020df3ca2ec4b064289408cfb0c2f8b299",
"workflows": [
{
"path": ".github/workflows/keep-ci.yml",
"jobs": [
"fmt, clippy, tests, release contract"
]
}
],
"qualification_blockers": [
"No merged current-head cloud functional/e2e qualification gate; GKE pilot PR #6 is unmerged.",
"No approved affected performance qualification gate. Existing unit/integration and release contract checks do not prove full MR qualification."
],
"lifecycle_mode": "failure-only"
}
36 changes: 36 additions & 0 deletions .github/workflows/issue-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Trusted default-branch controller only; never executes PR code or artifacts.
name: Issue CI handoff
on:
workflow_run:
workflows: ["keep-ci"]
types: [completed]
permissions:
actions: read
contents: read
pull-requests: read
issues: write
concurrency:
group: issue-ci-handoff
cancel-in-progress: false
jobs:
handoff:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- name: Validate trusted failure-only policy
run: python3 scripts/issue-ci/check-policy.py .github/issue-ci-required-gates.json
- name: Reconcile current PR heads from trusted configuration
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
shell: bash
run: |
set -euo pipefail
python3 scripts/issue-ci/handoff.py \
--repo "$REPO" --run-id "$RUN_ID" --reconcile \
--config .github/issue-ci-required-gates.json
4 changes: 2 additions & 2 deletions .github/workflows/keep-ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Pull-request and main CI: format, lint, the workspace tests, and the
# release contract. The issue loop's review step merges a PR only after these
# checks pass, and a PR without checks blocks its issue.
# release contract. CI lifecycle handoff requires the reviewed gate contract;
# independent review after success does not merge or change issue labels.
name: keep-ci

on:
Expand Down
1 change: 1 addition & 0 deletions scripts/issue-ci/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
48 changes: 48 additions & 0 deletions scripts/issue-ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# CI lifecycle handoff

Vendored without modification from `faberline/aw@67af8d020df3ca2ec4b064289408cfb0c2f8b299`
`plugins/faberline/skills/issue-ci/scripts/handoff.py`. SHA-256:
`c67533b39d45ad62d407af3748856be396941647c349b63464424fa6aff1c198`.

The exact existing required gate is `keep-ci` (`.github/workflows/keep-ci.yml`):
`fmt, clippy, tests, release contract`. This explicit manifest is the reviewed
policy source; visible green checks and branch protection are not inferred.

## Failure-only rollout

The guard imports the pinned shared controller's `load_config` and requires
its `evaluate_policy` API, explicit `lifecycle_mode: "failure-only"`, and a
nonempty `qualification_blockers` list. Old controllers, full mode, malformed
or missing policy, and clearing blockers refuse before the privileged call.
Confirmed current-head failures can return doing issues to todo with links.
Success, missing, skipped or pending evidence never writes done; already
done issues remain untouched. All gates and coverage gaps remain explicit.
The live gates above alone do not prove the approved MR policy (affected
unit, functional, e2e, performance).

- No merged current-head cloud functional/e2e qualification gate; GKE pilot PR #6 is unmerged.
- No approved affected performance qualification gate. Existing unit/integration and release contract checks do not prove full MR qualification.

Resolve these decisions in a separate reviewed change that supplies real
qualification and updates the explicit gate contract before clearing blockers
or enabling full mode. The controller itself suppresses done for any nonempty
blockers even if a future caller accidentally selects full mode.
No new cloud jobs, GKE triggers, performance runs, or credentials are added.
No docs-only exemption or zero-check pass is assumed.

AGY owns only todo → doing. CI aggregates every required workflow and job
for the current PR head; failure returns todo with run/job links. This
failure-only rollout never produces done. After separately approved full
qualification, review independently verifies the same head after success
and does not merge. Managed PRs must use
`issue/N` or `agy/issue-N` and include `Closes #N`. Unrelated labels remain.

The serialized controller reconciles all managed PRs on every wake, reads
only GitHub metadata, and uses latest run attempts. Missing, skipped, stale,
neutral, cancelled, or pending evidence cannot pass. It checks the head
before writes and journals handoffs for deduplication and recovery. Checkout
is the trusted default branch; PR code and artifacts are never executed or
used as permission evidence. The handoff workflow becomes effective only
after merge; this PR does not alter automation schedules.

Local validation: `python3 -m unittest discover -s scripts/issue-ci/tests -v`.
29 changes: 29 additions & 0 deletions scripts/issue-ci/check-policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Validate the trusted failure-only rollout without blocking real failures."""
import importlib.util
from pathlib import Path
import sys


def load_controller():
path = Path(__file__).with_name('handoff.py')
spec = importlib.util.spec_from_file_location('issue_ci_handoff', path)
controller = importlib.util.module_from_spec(spec)
spec.loader.exec_module(controller)
return controller


def check(path):
controller = load_controller()
if not all(callable(getattr(controller, name, None)) for name in ('load_config', 'evaluate_policy')):
raise RuntimeError('failure-only rollout requires the reviewed policy-aware controller')
policy = controller.load_config(path)
if policy.get('lifecycle_mode') != 'failure-only':
raise RuntimeError('this rollout requires explicit lifecycle_mode failure-only')
if not isinstance(policy.get('qualification_blockers'), list) or not policy['qualification_blockers']:
raise RuntimeError('this rollout requires an explicit nonempty qualification_blockers list')
return policy


if __name__ == '__main__':
check(sys.argv[1])
Loading
Loading