Retry HTTP 429 with backoff; keep models beyond first 3 as fallbacks; degrade agreement when some models fail; force-high risk only if zero usable responses. Clearer zh-CN errors/docs for dogfood uses: ./ rebase + workflow_dispatch re-run.
159 lines
5.8 KiB
YAML
159 lines
5.8 KiB
YAML
name: Gatekeep
|
||
description: >
|
||
Parallel multi-model AI PR review across providers. Agreed findings become
|
||
inline comments; disagreements stay in the summary. Fail-closed risk tier.
|
||
Self-contained remote uses: git-url@ref. Provider secrets via free-form env names.
|
||
author: Gatekeep
|
||
|
||
inputs:
|
||
github-token:
|
||
description: Token with pull-requests + checks write
|
||
required: true
|
||
config-dir:
|
||
description: >
|
||
Optional consumer config directory (e.g. .github/gatekeep with providers.yaml).
|
||
Empty = Action defaults/providers.yaml.
|
||
required: false
|
||
default: ""
|
||
models:
|
||
description: >
|
||
Optional override of 2–3 models as comma-separated provider:model
|
||
(or bare names unique in config). Empty = providers.yaml lists.
|
||
required: false
|
||
default: ""
|
||
language:
|
||
description: >
|
||
Report language for model prose + published summary/inline comments
|
||
(e.g. zh-CN, en). Empty = providers.yaml `language`, else en.
|
||
required: false
|
||
default: ""
|
||
skip-draft:
|
||
description: Skip draft PRs
|
||
required: false
|
||
default: "true"
|
||
run-verify:
|
||
description: Discover + run verify/benchmarks layer
|
||
required: false
|
||
default: "true"
|
||
retain-artifacts:
|
||
description: Upload last-good style artifacts from verify
|
||
required: false
|
||
default: "true"
|
||
|
||
outputs:
|
||
risk_tier:
|
||
description: Final published risk tier
|
||
value: ${{ steps.review.outputs.risk_tier }}
|
||
security_blocking:
|
||
description: Whether security check should block
|
||
value: ${{ steps.review.outputs.security_blocking }}
|
||
agreed_count:
|
||
description: Count of multi-model agreed findings
|
||
value: ${{ steps.review.outputs.agreed_count }}
|
||
|
||
runs:
|
||
using: composite
|
||
steps:
|
||
# Do not uses: actions/setup-node — many Gitea forges have no mirror (404).
|
||
# Runner image / consumer workflow must provide Node >= 18.
|
||
- name: Require Node.js
|
||
shell: bash
|
||
env:
|
||
GATEKEEP_LANGUAGE: ${{ inputs.language }}
|
||
run: |
|
||
if ! command -v node >/dev/null 2>&1; then
|
||
lang="${GATEKEEP_LANGUAGE:-}"
|
||
if [[ "${lang,,}" == zh* ]]; then
|
||
echo "::error::Gatekeep 需要 Node.js ≥ 18(merge-findings.js / publish.js)。"
|
||
echo "请在 runner 预装 Node,或在 uses: ./ 之前加本 forge 已镜像的 setup-node;不要依赖未镜像的 actions/setup-node。"
|
||
else
|
||
echo "::error::Node.js >= 18 required for Gatekeep (merge-findings.js / publish.js)."
|
||
echo "Preinstall Node on the runner, or add a mirrored setup-node step before uses: ./."
|
||
fi
|
||
exit 1
|
||
fi
|
||
node --version
|
||
|
||
# PEP 668 blocks pip --user on many runners; use a workspace venv instead.
|
||
- name: Install PyYAML (providers.yaml)
|
||
shell: bash
|
||
run: |
|
||
if python3 -c "import yaml" 2>/dev/null; then
|
||
python3 -c "import yaml; print('pyyaml ok', yaml.__version__)"
|
||
exit 0
|
||
fi
|
||
VENV="${{ github.workspace }}/.gatekeep/.venv"
|
||
mkdir -p "$(dirname "$VENV")"
|
||
python3 -m venv "$VENV"
|
||
"$VENV/bin/pip" install -q pyyaml
|
||
echo "$VENV/bin" >> "$GITHUB_PATH"
|
||
echo "VIRTUAL_ENV=$VENV" >> "$GITHUB_ENV"
|
||
"$VENV/bin/python" -c "import yaml; print('pyyaml ok', yaml.__version__)"
|
||
|
||
- name: Discover entry points
|
||
id: discover
|
||
shell: bash
|
||
env:
|
||
GATEKEEP_ROOT: ${{ github.workspace }}
|
||
GATEKEEP_ACTION: ${{ github.action_path }}
|
||
CONFIG_DIR: ${{ inputs.config-dir }}
|
||
run: bash "${{ github.action_path }}/scripts/discover.sh"
|
||
|
||
- name: Policy floor + security scan
|
||
id: floor
|
||
shell: bash
|
||
env:
|
||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||
CONFIG_DIR: ${{ inputs.config-dir }}
|
||
GATEKEEP_ROOT: ${{ github.workspace }}
|
||
GATEKEEP_ACTION: ${{ github.action_path }}
|
||
run: bash "${{ github.action_path }}/scripts/policy-floor.sh"
|
||
|
||
- name: Parallel multi-model review
|
||
id: review
|
||
shell: bash
|
||
env:
|
||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||
CONFIG_DIR: ${{ inputs.config-dir }}
|
||
GATEKEEP_MODELS: ${{ inputs.models }}
|
||
GATEKEEP_LANGUAGE: ${{ inputs.language }}
|
||
SKIP_DRAFT: ${{ inputs.skip-draft }}
|
||
GATEKEEP_ROOT: ${{ github.workspace }}
|
||
GATEKEEP_ACTION: ${{ github.action_path }}
|
||
POLICY_FLOOR: ${{ steps.floor.outputs.floor }}
|
||
POLICY_REASONS: ${{ steps.floor.outputs.reasons }}
|
||
SCAN_HARD_FAIL: ${{ steps.floor.outputs.scan_hard_fail }}
|
||
DISCOVER_JSON: ${{ steps.discover.outputs.discover_json }}
|
||
# Provider API keys: set free-form names from providers.yaml `key`
|
||
# on this step's env (inherited from the workflow step). Example:
|
||
# env:
|
||
# OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||
# MY_CUSTOM_KEY: ${{ secrets.MY_CUSTOM_KEY }}
|
||
run: |
|
||
export PATH="$HOME/.local/bin:$PATH"
|
||
# Ensure user-site packages (PyYAML) are importable
|
||
export PYTHONPATH="$(python3 -c 'import site; print(site.getusersitepackages())'):${PYTHONPATH:-}"
|
||
bash "${{ github.action_path }}/scripts/review.sh"
|
||
|
||
- name: Verify layer (benchmarks)
|
||
if: inputs.run-verify == 'true'
|
||
id: verify
|
||
shell: bash
|
||
env:
|
||
GATEKEEP_ROOT: ${{ github.workspace }}
|
||
CONFIG_DIR: ${{ inputs.config-dir }}
|
||
GATEKEEP_ACTION: ${{ github.action_path }}
|
||
DISCOVER_JSON: ${{ steps.discover.outputs.discover_json }}
|
||
run: bash "${{ github.action_path }}/scripts/verify-benchmarks.sh"
|
||
|
||
- name: Retain artifacts
|
||
if: inputs.retain-artifacts == 'true' && always()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: gatekeep-last-good-${{ github.run_id }}
|
||
path: |
|
||
${{ github.workspace }}/.gatekeep/out/
|
||
${{ github.workspace }}/.gatekeep/verify/
|
||
if-no-files-found: ignore
|
||
retention-days: 14
|