name: attest
description: Skip a CI job when a signed note proves its gate already passed on this exact tree
branding:
icon: check-circle
color: green
inputs:
signers:
description: >-
Path to an allowed_signers file, relative to the repository root.
Default: .forgejo/allowed_signers, then .github/allowed_signers.
required: false
default: ""
principal:
description: >-
Accept only an attestation signed by this identity. Default: whoever the
signature says, provided that key is in the signers file — so a file
naming a whole team just works, and there is nothing to keep in sync.
required: false
default: ""
platform:
description: >-
Only accept an attestation minted on this platform, as <arch>-<os>
(e.g. x86_64-linux). Default: the runner this step is on, so a matrix leg
skips only work that really ran on its own platform. An OS alone
(`linux`) accepts any architecture. Pass `any` to state that the whole
suite's result does not depend on where it ran.
required: false
default: ""
anywhere:
description: >-
Gate names, space-separated, whose result cannot depend on where they
ran: formatting, shell lint, secret scanning, dependency audit. A
verified attestation from ANY platform covers these, so a laptop's note
skips them on every leg of a matrix. This is your committed statement
about those checks, made once, here. Never list a check that compiles or
executes the product — tests, clippy, a typecheck under cfg, a build —
whatever its name says.
required: false
default: ""
outputs:
gates:
description: >-
The covered gate names as a JSON array — `[]` when nothing is covered.
USE THIS ONE, in an `if:` expression:
"not contains(fromJSON(steps.attest.outputs.gates), 'your-gate')".
`contains` over an array matches ELEMENTS; over the `covered` string
below it matches substrings, so an unrelated gate named
`pre-push-cargo-test-slow` would satisfy a check for
`pre-push-cargo-test` and skip the real suite. See the README for the
expression written out — it cannot be spelled here, because GitHub
EVALUATES a dollar-brace expression inside this description rather than
displaying it, and the action then fails to load.
value: ${{ steps.verify.outputs.gates }}
covered:
description: >-
The same names, space-separated. Kept only so workflows written against
the older inline templates keep working; prefer `gates`.
value: ${{ steps.verify.outputs.covered }}
runs:
using: composite
steps:
- id: verify
shell: bash
env:
ATTEST_SIGNERS: ${{ inputs.signers }}
ATTEST_PRINCIPAL: ${{ inputs.principal }}
ATTEST_PLATFORM: ${{ inputs.platform }}
ATTEST_ANYWHERE: ${{ inputs.anywhere }}
run: |
set -u
# Positional parameters rather than a bash array: macOS runners can
# still put bash 3.2 first on PATH, where an empty array under `set -u`
# is an error. `"$@"` is exempt from nounset in every POSIX shell.
set --
if [ -n "$ATTEST_SIGNERS" ]; then set -- "$@" --signers "$ATTEST_SIGNERS"; fi
if [ -n "$ATTEST_PRINCIPAL" ]; then set -- "$@" --principal "$ATTEST_PRINCIPAL"; fi
if [ -n "$ATTEST_PLATFORM" ]; then set -- "$@" --platform "$ATTEST_PLATFORM"; fi
if [ -n "$ATTEST_ANYWHERE" ]; then set -- "$@" --anywhere "$ATTEST_ANYWHERE"; fi
# Reasons go to stderr and land in the job log. When an expected skip
# does not happen, that log says which of tree, platform, signature or
# signers was the reason — the alternative is a silent non-answer.
bash "$GITHUB_ACTION_PATH/verify.sh" --github-output "$@" >> "$GITHUB_OUTPUT"