Skip to content

ci: all CI on our pf-ci pool with the canonical authz guard (no hosted fallback) - #146

Merged
luisleo526 merged 13 commits into
mainfrom
data-vp/ci-pool-canon-tests
Oct 20, 2026
Merged

luisleo526 merged 13 commits into
mainfrom
data-vp/ci-pool-canon-tests

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What

All CI of this repo runs on our own machines (pf-ci pool), pull requests included, and every workflow starts with the canonical authz job from the org CI-label file (ci-labels.md, #808).

  • Runners. Every job that had its own runs-on now uses the plain pool labels ([self-hosted, pf-ci, pf-ci-x64]): no expression, no hosted fallback. Before, pull requests were forced onto ubuntu-24.04 and only pushes used the pool. ci.yml (changes, test, docker, database-e2e, contract, distribution, ready), docs.yml (build), native-feed.yml (asan-ubsan, tsan, release matrix Linux x64 leg).
  • authz. First job of ci.yml, docs.yml, native-feed.yml, copied from the canon d7ba0680. pull_request_target and any event not in the canon's list fail red; a pull request from another repository or from a fork fails red; push, workflow_dispatch, schedule, merge_group, release, workflow_call and repository_dispatch pass. workflow_run passes only when the triggering run's head repository is this repository and its triggering actor is an allowlisted bot or has admin/maintain/write on this repository, read with one gh api repos/<repo>/collaborators/<actor>/permission call; a failed lookup refuses. permissions: {} — no workflow permission is granted, and GH_TOKEN: ${{ github.token }} serves only that read-only lookup. ALLOWED_BOTS: "dependabot[bot] pineforge-app[bot]" (same-repository pull requests only). A pull_request from this repository passes when author_association is OWNER/MEMBER/COLLABORATOR or the author is an allowlisted bot; otherwise it passes only with admin/maintain/write read from GitHub — a member whose org membership is private shows as CONTRIBUTOR, and a failed lookup refuses. Every other job needs: authz. release/ready fails unless authz succeeded. The four runtime jobs (docker, database-e2e, contract, distribution) also require needs.authz.result == 'success' in their if: (the !cancelled() they use replaces the implicit success check, so without this they ran on a push even if authz failed).
  • Tests. The workflow policy tests pinned the old rules (PRs on hosted runners, no secrets, no needs); they now pin the new ones, and each changed pin quotes its old assertion and has new rejected cases.

Native feed runs of this PR

native-feed.yml is paths-filtered and not a required check (the required authz is in ci.yml, which has no filter). Its runs on this PR were CANCELLED by @pineforge-ci-dispatcher[bot] (raw annotation: "The run was canceled by @pineforge-ci-dispatcher[bot]."): the Release (ubuntu-24.04-arm) job asks for pf-ci-arm64, a class that is not live yet, and the whole run (the x64 legs and the macOS leg too) was cancelled. laneboss confirms this is a known dispatcher defect: a job that targets a class that is not live cancels the whole run. Native feed runs are cancelled by the dispatcher until pf-ci-arm64 is live (about 21:00 tonight, laneboss); re-run after. After that a refused-class job stays queued and a re-run keeps all legs. native-feed.yml is intentionally not changed for this (owner CI-ARM = C).

Barrier and visible check

  • The barrier against outside contributors is the org/repo setting "Require approval for all external contributors" (all_external_contributors, read by L6 on the org and on this repo): a fork run waits in action_required before any job starts. Second layer: laneboss's dispatcher identity gate (the pineforge-ci-dispatcher app), which also admits actors (members, collaborators, dependabot[bot], the release App pineforge-app[bot]). Its state read 2026-10-20 16:50 Taipei: it posts pf-ci gate on this PR's head 442d24ff (failure, summary label_mismatch, 08:44:12Z) — the label gate, not the identity gate, refuses right now.
  • authz is the visible red check for a member's own fork and for pull_request_target.
  • ci.yml has no paths:/branches: filter on pull_request (ci.yml line 6), so the required authz check runs on every PR. docs.yml has none either. native-feed.yml keeps its paths: filters (lines 6 and 8); its own authz is a guard there, not the required check.
  • Why the four runtime jobs carry needs.authz.result == 'success': they can be skipped only when authz has already failed, and the red required authz then blocks the merge anyway (canon item 3); without the condition they would run on the pool after a failed authz on a push.
  • Rule 3a (jobs with always() / !cancelled() that need authz): release/ready in ci.yml (line 268, if: always()) now starts with a dedicated refusal step (line 276: if: needs.authz.result != 'success', run: exit 1) before any checkout; its first gate step also exits 1 on a failed authz; its checkout (line 327), setup-python and the evidence aggregation step require needs.authz.result == 'success'. The four runtime jobs docker (line 104), database-e2e (128), contract (187), distribution (210) use !cancelled() and are covered by needs.authz.result == 'success' in their own if: (they run no step when authz failed). docs.yml and native-feed.yml have no always() or !cancelled() job.
  • Repository setting read-back (proof item 3, L6): the fork-PR approval policy is "all_external_contributors" on the org and on pineforge-4pass/pineforge-data.
  • authz is the frozen org canon ci-labels.md d7ba0680 (sha256 d7ba0680766031a4416f5d24da6ec4d392784ab0dba474d422c51128ed2c28c0) copied as is in ci.yml, docs.yml and native-feed.yml; the only per-repository edit is ALLOWED_BOTS: "dependabot[bot] pineforge-app[bot]" (owner #801 and CI-RELBOT = A). The pull_request arm also asks GitHub for the author's permission, for members shown as CONTRIBUTOR. Proof (canon list): item 1/2 = the policy pins in tests/test_pr_tier_workflows.py and tests/test_release_adapter.py (authz copy-as-is pin line by line with comments, structure and runner pins with rejected cases, the authz script executed with a fake gh for the decision cases with fail(), allowlist and permission-list mutations) and ci-policy-check.py (tool sha b3adc5cc) run as python3 -I ci-policy-check.py --canon ci-labels.md --not-required docker,database-e2e,contract,distribution <repo>: it leaves only R6 for docs.yml deploy in these three workflows (raw: ".github/workflows/docs.yml": job="deploy" step=- R6: runs-on must resolve to canonical labels or a macOS hosted label; no fallback) plus the release/pineforge-release/release-report workflows, which VP-111 (merged first) covers; the four runtime jobs are --not-required (R7) because the required checks are authz and release/ready.
  • Deviation, named: the heavy jobs keep job-level if: conditions that can skip them (docs-only PRs skip the four runtime jobs; the runtime jobs also carry !cancelled() && needs.authz.result == 'success'). They are not required checks; the required checks are authz and the aggregate release/ready (if: always(), refuse-first), which fails unless each of them is success, or skipped for the declared docs-only reason after authz success. A decision test pins that a skipped job without the docs-only reason makes the aggregate red.
  • Known residual risk: for pull_request GitHub runs the workflow file from the PR merge ref, so a fork PR could change this file; only the approval policy and the dispatcher stop it, not YAML.

ci-policy-check R6, pending pf-ci-release (tool sha256 b3adc5cc7340f0071ba78a5029e7db0e4702709789cb614a59b52e18b2277f56)

ci-policy-check.py on this branch reports no R1-R5 finding. The only findings left are R6 ("runs-on must resolve to canonical labels or a macOS hosted label; no fallback") for jobs that stay hosted on purpose until laneboss's pf-ci-release class is live; they use release credentials, App tokens, OIDC or environments and are NOT touched here:

  • docs.yml: deploy (Pages environment, deploy-pages)
  • pineforge-release.yml: reconcile
  • release-report.yml: report
  • release.yml: claim-name, manual-pr, prepare, build, github-release, publish, verify

(native-feed.yml release is resolved here: every matrix leg has its own runner, macos-14 is the one hosted macOS leg.) always() is used by the aggregate job release/ready (not !cancelled()): a cancelled run skips a !cancelled() job and GitHub counts a skipped required check as success; ready refuses first when authz did not succeed.

Pending / follow-ups (not in this PR)

  • native-feed.yml release matrix: ubuntu-24.04-arm runs on [self-hosted, pf-ci, pf-ci-arm64] (owner choice CI-ARM = C, never GitHub). The label is listed in ci-labels.md (laneboss 5cb38079); the runner goes live at about 21:00 tonight (laneboss) and the leg queues until then. macos-14 stays on GitHub (no Mac hardware).
  • Labels stay pf-ci-x64 until laneboss posts "pf-ci-crj live"; then the non-Docker jobs move to pf-ci-crj.
  • Follow-up when pf-ci-release is live: docs.yml build (uploads the Pages artifact that deploy publishes) moves there; release.yml build, github-release, verify and the jobs with secrets/environments (claim-name, manual-pr, prepare, publish, report, release-report.yml, pineforge-release.yml) are not touched here. docs.yml deploy (Pages environment) stays on ubuntu-latest for now.
  • ci.yml distribution builds evidence (the release rebuilds the published distribution in release.yml build), so it stays on the pool.

🤖 Generated with Claude Code

luisleo526 and others added 13 commits October 20, 2026 17:06
Every job that had its own runs-on in ci.yml, docs.yml and native-feed.yml now
uses the plain pool labels ["self-hosted","pf-ci","pf-ci-x64"]: no expression,
no hosted fallback, pull requests included (they were forced to ubuntu-24.04
before), dependabot included. The native-feed release matrix runs its Linux
x64 leg on the x64 pool labels and its Linux arm64 leg on
["self-hosted","pf-ci","pf-ci-arm64"]; only macos-14 stays on GitHub.
docs.yml deploy (Pages environment) is unchanged.

Each of the three workflows gets a first job `authz` that fails red (exit 1,
never a skip) unless the triggering actor is dependabot[bot] or a repository
collaborator with admin, maintain or write permission, checked with one call to
GET /repos/{repo}/collaborators/{actor}/permission using the default
GITHUB_TOKEN (no new secret). Fork pull requests fail red without a lookup; an
API error or an empty answer fails red. Every other job needs authz, and ci.yml
`ready` fails unless authz succeeded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
docker, database-e2e, contract and distribution use `!cancelled() && ...` in
their `if:`, which replaces the implicit success() over `needs`. On a push where
authz failed they would still have run on the pool. Add
`needs.authz.result == 'success' &&` to those four conditions so that no job
runs unless authz passed (owner rulings B / #800 / #801).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…orkflow tests

The workflow tests pinned the old policy (pull requests forced onto hosted
runners, no secrets, no needs). Update them to the owner rulings: every job
uses the plain pool labels without a hosted fallback, authz is needed by every
job and gates release/ready and the runtime jobs, authz may use only
secrets.GITHUB_TOKEN with contents: read, and its bot allowlist is the exact
dependabot[bot] and pineforge-app[bot]. Each changed pin quotes its old
assertion and gains a new rejected case; no test is deleted or loosened.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Replace the API based authz of the three workflows with the canonical job
(ci-labels.md, #808): on the pf-ci-x64 pool, pull_request_target fails red, a
pull request whose head repository is not this repository fails red, every
other event passes. Actors (members, collaborators, dependabot[bot], our
release App) are admitted or refused by the dispatcher, not in YAML, and the
only secret-free job text is the canonical one. Every other job still needs
authz; ci.yml has no paths filter on pull_request and holds the required
check, docs.yml and native-feed.yml keep their own authz as a guard.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ailed authz

The workflow tests pinned the earlier API based authz. They now pin the
canonical same-repository job from ci-labels.md: exact keys, pool labels,
single named step, exact env and script, no secrets or permissions anywhere
except the Pages deployment, and ci.yml/docs.yml without a pull_request
filter (the required authz must run on every PR; native-feed keeps its paths
as a guard). Each rewritten pin quotes the removed one and gets new rejected
cases.

release/ready is the only always() job: following ci-labels.md rule 3a its
first step now refuses (exit 1) unless authz succeeded, before any checkout,
and its checkout and repo-code steps also require a successful authz. The
four runtime jobs carry needs.authz.result == 'success' in their own if.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… #830)

A superseded run that concurrency cancelled must not leave the aggregate job
queued for a runner and block the new head's run. `if: ${{ !cancelled() }}`
still runs release/ready (red) when authz failed; its first step still refuses
unless authz succeeded, before any checkout. Pins that asserted always() now
assert the new condition (old line quoted), and a new pin forbids always() in
the three PR-tier workflows with a rejected case. The canon authz block was
re-compared with ci-labels.md ff778494 and is unchanged in all three files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ier workflow

The previous commit message claimed a rejected case for the new pin; only
ready was covered, by another assertion. Add the case for all three
workflows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tep on authz

Rule 3a / ci-policy-check R5: docker, database-e2e, contract and distribution
start with the canonical refusal step and every checkout, local-action and run
step requires needs.authz.result == 'success'; release/ready's gate step too.
Pins extended to the five jobs with rejected cases.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…runner per leg

authz is the canonical job copied as is (ALLOWED_BOTS dependabot[bot] and
pineforge-app[bot]) in ci.yml, docs.yml and native-feed.yml. release/ready is
back to if: always() (a cancelled run must not skip the required aggregate).
native-feed's release matrix gives every leg its own runner (x64 list, arm64
list, macos-14) and runs-on: ${{ matrix.runner }}. Pins for always() flipped;
the authz and matrix pins follow in the next commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e pull_request arm)

Only the pull_request arm changes (ci.yml, docs.yml, native-feed.yml);
ALLOWED_BOTS stays dependabot[bot] pineforge-app[bot].

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ssion lookup)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s included

Claude read of the delta: the helper that dropped comment lines weakened the
copy-as-is pin. Back to full-text equality, now line by line so newlines (shell
syntax) are not collapsed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@luisleo526
luisleo526 force-pushed the data-vp/ci-pool-canon-tests branch from 442d24f to b21be27 Compare October 20, 2026 09:07
@luisleo526
luisleo526 merged commit 9ffac89 into main Oct 20, 2026
14 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant