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
69 changes: 52 additions & 17 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,64 @@ on:
push:
branches: [develop, main]

permissions:
contents: read

jobs:
pytest:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: agent
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U agent"
--health-interval 5s
--health-timeout 5s
--health-retries 10
runs-on: [self-hosted]
env:
AGENT_TEST_PG: host=127.0.0.1 port=5432 user=agent dbname=postgres
A38_TEST_DOCKER: '1'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e ".[test]"
- run: pytest
- name: Preflight runner tools
run: |
set -euo pipefail
# Functional Homebrew toolpaths when present (macOS). Not private hosts.
if [ -d /opt/homebrew/bin ]; then
echo "/opt/homebrew/bin" >> "$GITHUB_PATH"
export PATH="/opt/homebrew/bin:$PATH"
fi
if [ -d /opt/homebrew/opt/postgresql@17/bin ]; then
echo "/opt/homebrew/opt/postgresql@17/bin" >> "$GITHUB_PATH"
export PATH="/opt/homebrew/opt/postgresql@17/bin:$PATH"
elif [ -d /opt/homebrew/opt/postgresql@16/bin ]; then
echo "/opt/homebrew/opt/postgresql@16/bin" >> "$GITHUB_PATH"
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
fi
# Linux runners use already-installed system PostgreSQL/Docker paths.
if ! command -v initdb >/dev/null; then
echo "PostgreSQL binaries (initdb) must already be on PATH on this self-hosted runner." >&2
echo "Install them outside CI; this workflow does not run apt or brew." >&2
exit 1
fi
if ! command -v pg_ctl >/dev/null; then
echo "PostgreSQL binaries (pg_ctl) must already be on PATH on this self-hosted runner." >&2
echo "Install them outside CI; this workflow does not run apt or brew." >&2
exit 1
fi
if ! command -v docker >/dev/null; then
echo "Working Docker must already be installed on this self-hosted runner." >&2
echo "Install and start it outside CI; this workflow does not run apt or brew." >&2
exit 1
fi
if ! docker info >/dev/null 2>&1; then
echo "Docker is installed but the daemon is not reachable on this self-hosted runner." >&2
exit 1
fi
- name: Install and test in isolated venv
run: |
set -euo pipefail
VENV="${RUNNER_TEMP}/agent-test-venv-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
python -m venv "$VENV"
# shellcheck disable=SC1091
source "$VENV/bin/activate"
python -m pip install --disable-pip-version-check -e ".[test]"
# Use tests/conftest.py ensure_cluster (temporary local PostgreSQL).
unset AGENT_TEST_PG
pytest
15 changes: 13 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@
- Public repository: English for commits and comments. The visible pull-request summary is an `EN:` block, optionally followed by a labeled `DE:` block.
- Do not name private repositories, internal hostnames, or internal infrastructure.
- Add or update tests in the same change.
- Run `pytest` on the exact clean signed final head before Ready for review. Tests need PostgreSQL (`AGENT_TEST_PG` or a local `initdb`). Full pytest is not a gate for the first draft publication.
- Run `pytest` on the exact clean signed final head before Ready for review. With `AGENT_TEST_PG` unset, `tests/conftest.py` starts a temporary local PostgreSQL cluster via `ensure_cluster` (needs `initdb` / `pg_ctl` on `PATH`). Full pytest is not a gate for the first draft publication.
- Pytest (or any green local suite) is a **check**, not Ready for review and not completion.

## Self-hosted GitHub Actions runners

This repository's workflows use `runs-on: [self-hosted]` only (no GitHub-hosted fallback). GitHub orchestrates jobs and shows statuses; the process runs on an adopter-operated machine. Register and label runners outside this package — there is no production runner installer here.

CI prerequisites on every runner that executes `.github/workflows/test.yml`:

- PostgreSQL client/server binaries already on `PATH` (`initdb`, `pg_ctl`, and related tools). On macOS, existing Homebrew paths such as `/opt/homebrew/bin` and `/opt/homebrew/opt/postgresql@17/bin` (or `@16` when that is what is installed) are accepted when present. On Linux, use the distribution's already-installed PostgreSQL tool paths.
- A working Docker installation with a reachable daemon (`A38_TEST_DOCKER=1` is mandatory in CI and must not be skipped).

The workflow preflight fails loudly when those tools are missing. It does **not** run `apt`, `brew`, or other package installs. Keep shared global Python environments untouched: CI creates an isolated venv under `RUNNER_TEMP` keyed by `GITHUB_RUN_ID` / `GITHUB_RUN_ATTEMPT`.

## Ready for review

A draft plus local tests is not done. Do not claim the pull request is finished, done, or completed at that point — including after leave-draft. Draft timing and CI ownership while the draft is open are defined in [docs/pull-request-lifecycle.md](docs/pull-request-lifecycle.md).
Expand All @@ -21,7 +32,7 @@ Ready for review requires all of:
3. Codex runs only if both grok dimensions are approved. If a vendor cannot run, abort loudly; do not record `approved`; do not substitute another vendor.
4. Zero findings only after an explicit complete pass. Empty, partial, timeout, or unavailable output is not zero findings. Iterate until all four lane verdicts on this head are approved.
5. Inner implement/review rounds (`review-loop`) are not the PR reviews (`pr-review`).
6. CI green on **this** head. This public repository uses GitHub Actions. `skipped` and `cancelled` are not green unless the workflow documents that skip. The local-CI comment schema for **private** product repositories is defined in [docs/local-ci-v1.md](docs/local-ci-v1.md) and verified by `agent local-ci verify`.
6. CI green on **this** head. This public repository uses GitHub Actions with self-hosted runners for job execution. `skipped` and `cancelled` are not green unless the workflow documents that skip. The local-CI comment schema for **private** product repositories is defined in [docs/local-ci-v1.md](docs/local-ci-v1.md) and verified by `agent local-ci verify`.
7. Stay draft until the reviews and CI above hold on this head. Then one comment whose review-pass count is those four `approved` verdicts on this head, then mark the GitHub pull request ready for review (`isDraft=false`). When spine and pr-review are attached, `agent allow --action pr-ready` only checks task state (`pushing` or `pr-review`); it is not the leave-draft verdict. Do not mark ready if it denies. Ready for review is still not merge and not completion.
8. A human merges. Claim pull-request completion only after that merge is verified. When spine is attached, `agent allow --action task-done` still needs the workflow checklist and both summary sentences; that ledger state is not proof of pull-request completion.

Expand Down
6 changes: 4 additions & 2 deletions docs/a38-guard.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ dfx pr guard explains a repository's centrally defined [A38 rules](a38.md), chec

Install the [example workflow](../examples/a38-guard.yml) on the target repository's default branch. Replace `USES_REF_PIN_ME` with a reviewed, published **full commit SHA** of this repository. The example is not deployable until that placeholder is replaced. Keep the guard's executable action pinned even when approving policy migrations.

The example job uses `runs-on: [self-hosted]` so the guard process runs on an adopter-operated machine. GitHub still orchestrates the workflow and displays check statuses; it must not be the execution host. Adopters may append their own runner labels or groups after `self-hosted`. That routing belongs in the adopter's workflow configuration only — the guard runtime never hardcodes runner labels, hostnames, or private repository names. Do not put private infrastructure names into public documentation or the shared example.

The [composite action](../.github/actions/a38-guard/action.yml) uses pinned setup-python and PyYAML 6.0.2, and imports only the trusted action's sources through `github.action_path/../../../src`. Both Python steps run from the trusted action directory with safe-path mode (`python -P`), and replace inherited `PYTHONPATH` with the trusted source path, preventing consumer modules from shadowing the guard or its installer. It does not install dependencies or run scripts from the consumer checkout. Install the package's declared dependencies for standalone use; there is no fallback YAML parser.

The guard's comment and JSON expose three different immutable links:
Expand All @@ -22,14 +24,14 @@ Standalone execution accepts the same explicit trusted `A38_RUNTIME_REVISION`. W

The token requires contents read, pull requests write, issues write and statuses write. Publishing the guard comment on a pull request needs `pull-requests: write` for `GITHUB_TOKEN`; `issues: write` alone is not enough and yields 403. Policy migrations also require permission to read collaborators' effective repository permissions. If that API is unavailable, the migration fails closed. Use a dedicated GitHub App or service account with the necessary repository access for external operation. Tokens are taken from `GH_TOKEN` or `GITHUB_TOKEN` and never printed.

Actions must actually be available for event-driven operation. When Actions are blocked or unavailable, run the same reconciler on a trusted external host:
Actions must actually be available for event-driven operation, with jobs scheduled onto the adopter's self-hosted runners. When Actions are blocked or no suitable self-hosted runner is registered, run the same reconciler on a trusted machine the adopter controls:

```sh
agent pr-guard --repo OWNER/NAME --all-open --dry-run
agent pr-guard --repo OWNER/NAME --all-open
```

Schedule that command externally when Actions are unavailable; no daemon is installed. The example workflow also reconciles all open PRs at minutes 17 and 47 of every hour and serializes all bot runs for the repository. Its manual dispatch accepts either a PR number or `all_open=true`. GitHub Actions does not guarantee delivery of every pending concurrency event, so scheduled reconciliation recovers missed events, base changes and permission changes. Immutable SHA-addressed contents and trees are cached within the API client, up to 128 entries; comments, reviews, permissions and PR snapshots are never cached.
Schedule that command externally when Actions are unavailable; no daemon is installed and this package does not ship a production runner installer. The example workflow also reconciles all open PRs at minutes 17 and 47 of every hour and serializes all bot runs for the repository. Its manual dispatch accepts either a PR number or `all_open=true`. GitHub Actions does not guarantee delivery of every pending concurrency event, so scheduled reconciliation recovers missed events, base changes and permission changes. Immutable SHA-addressed contents and trees are cached within the API client, up to 128 entries; comments, reviews, permissions and PR snapshots are never cached.

## Trust and policy

Expand Down
4 changes: 3 additions & 1 deletion examples/a38-guard.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Example consumer workflow for dfx pr guard.
# Before rollout: replace USES_REF_PIN_ME with a pinned commit SHA of DFXswiss/agent.
# Do not check out the pull-request head for this job.
# runs-on starts at [self-hosted]. Adopters may append their own runner labels or
# groups; routing is adopter configuration, not hardcoded in the guard runtime.

name: dfx pr guard

Expand Down Expand Up @@ -35,7 +37,7 @@ permissions:

jobs:
guard:
runs-on: ubuntu-latest
runs-on: [self-hosted]
steps:
- name: dfx pr guard
uses: DFXswiss/agent/.github/actions/a38-guard@USES_REF_PIN_ME
Expand Down
Loading