Adoption - ChiefGyk3D/scrum-around-and-find-out GitHub Wiki
Generated from docs/adoption.md at df1dbe1. Edit the repository file, not the wiki.
Four steps: create an App, write a board.yaml, run bootstrap, add the caller. Nothing is hosted; the Action runs in your
workflow with your App's key.
- Open your organization's Settings, Developer settings, GitHub Apps, New GitHub App. Name it, set the homepage to your repository, and uncheck Webhook, Active: the App receives no events.
- Permissions. Organization: Projects, read and write. Repository: Issues, read; Pull requests, read. Metadata, read, is added for you. Nothing else.
- "Where can this GitHub App be installed?": Only on this account.
- Note the Client ID (an identifier, not a secret). Generate a private key (a
.pem). - Install the App on the organization and choose Only select repositories: the ones in your
board.yaml. - Store the key in your secret store (a repository secret, or an OIDC-fetched store) and delete the downloaded file.
A repository the App is not installed on answers NOT_FOUND to the App's token. audit reports it as UNKNOWN and
reconcile names it and exits 1, so a missing installation is loud, not silent.
Start from examples/renegade-penguin.yaml and the reference. Leave
project.number out to have bootstrap create the project.
gh auth refresh -s project # once: the project scope
safo --board board.yaml bootstrap # creates what is missing; prints the UI-only checklist
safo --board board.yaml audit # 0 clean, 1 drift, 2 cannot tell
safo --board board.yaml reconcile # add every open issue and PR, mark closed ones Donebootstrap never edits an existing field's options. If it refuses something it prints the step to do in the project's
settings, and exits 1.
Call the Action directly from the repository that holds board.yaml:
name: Project board
on:
issues:
types: [opened, reopened, closed, edited]
schedule:
- cron: '17 5 * * 1'
workflow_dispatch:
permissions:
contents: read
jobs:
board:
runs-on: ubuntu-latest
steps:
# First step: a composite action cannot be the first step of a job, so it cannot harden the runner itself.
- uses: step-security/harden-runner@<sha> # vX.Y.Z
with:
egress-policy: block
allowed-endpoints: api.github.com:443 github.com:443 pypi.org:443 files.pythonhosted.org:443
- uses: actions/checkout@<sha> # vX.Y.Z
with:
persist-credentials: false
- uses: ChiefGyk3D/scrum-around-and-find-out@<sha> # vX.Y.Z
with:
mode: ${{ github.event_name == 'issues' && 'sync' || 'reconcile' }}
board-file: board.yaml
client-id: ${{ vars.BOARD_APP_CLIENT_ID }}
private-key: ${{ secrets.BOARD_APP_PRIVATE_KEY }}Pin by commit with the version in a comment; Dependabot moves both. Pull requests reach the board through the weekly
reconcile. To react to pull-request events as well, use pull_request_target and read the trust boundary below first:
the board-less inputs (project-url and the status inputs, no board-file) exist for that job, which needs no checkout.
Never run untrusted code (a PR checkout, a build, a script) in the same job before the SAFO Action. With
pull_request_target, run SAFO in its own job with no checkout of PR code. The job/runner boundary is the trust boundary:
a same-user process from an earlier step can mutate the Action's venv or read credential-step environments.
The Action does what a composite action can: it builds a fresh venv in a new private directory, runs Python isolated,
checks the venv before and after the install, masks every credential, and passes inputs only through environment
variables. It cannot defend against a step that already runs as the same user in the same job, because that step can
change the venv after it is created or read the environment of the steps that hold the key. Keep the job that holds the
App key free of anything you do not trust. The Action accepts only the modes that talk to the board (validate,
bootstrap, audit, sync, reconcile, status; its preflight also lets agents-status through, which has nothing
to read on a runner). The local modes (usage, route, outcome, local, hooks, profiles) are not allowed in the Action. See
Limits for the rest of what is accepted.
If your repositories already call the shared workflows in
git-your-ship-together, its project-sync.yml is a thin wrapper
around this Action with the same inputs it always had; bump the pin.
Set app-owner to the organization and the token is minted from the organization's App installation, so a public
repository on a personal account can sync to the organization's board. The token is never installation-wide, so it
needs a repository scope, and the calling repository cannot be one (it belongs to the personal account). Name the
organization's repositories in repositories (names only, no owner), or list them in board.yaml. The token then
carries only the one organization-projects permission, limited to those repositories, and the repository must be public
because the organization's App can read a personal account's issues and pull requests only then. Without a scope the
run stops and says so.
A GitHub App has no user-account Projects permission, so an App cannot write a board owned by a user. For that one case the
Action accepts a fine-grained personal access token as token, with Projects read and write and Issues and Pull requests
read. It is refused for an organization board unless you also set allow-token-for-org: true, and safo refuses an App
token for a user board before it makes a request.
Better still, keep a user-owned board current from your own machine: safo reconcile with gh auth token, on a schedule
you run (cron or a systemd timer). No token is stored anywhere.
-
safo bootstrap, then do the UI checklist it prints. -
safo audituntil it printsclean. -
safo --dry-run reconcile: every change is printed asdry run: would ..., none is sent. -
safo reconcile. Run it again: it reportsnothing to do. - Open a test issue. It appears as Backlog within a minute. Close it: Done, with today's date.