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: ""
include-local:
description: >-
`true` also reads refs/notes/attest-local/*: blocks a `sign` step wrote
with `push: false`, which never reached origin and so cannot be revoked
there. Off by default, so a persistent runner's leftovers never cover a
later job. Only `true`, `false` or empty; anything else fails the step.
required: false
default: "false"
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')";
the `|| '[]'` keeps the expression valid when this step was skipped.
`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 }}
notes:
description: >-
What became of refs/notes/amont-attest, origin's mirror: fetched, absent
(origin has none; the local mirror was deleted), unreachable (origin did
not answer; nothing was judged), unwritable (origin's copy could not
replace a stale local one, a leftover lock say; nothing was judged),
undeletable, or no-origin.
value: ${{ steps.verify.outputs.notes }}
inputs_notes:
description: The same, for refs/notes/amont-attest-inputs.
value: ${{ steps.verify.outputs.inputs_notes }}
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 }}
ATTEST_INCLUDE_LOCAL: ${{ inputs.include-local }}
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
# Strict, like an unknown flag: a typo such as `ture` that silently
# meant `false` would just never cover anything.
case "$ATTEST_INCLUDE_LOCAL" in
true) set -- "$@" --include-local ;;
false | "") ;;
*) echo "attest: include-local must be true or false, not '$ATTEST_INCLUDE_LOCAL'" >&2; exit 2 ;;
esac
# 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.
out=$(bash "$GITHUB_ACTION_PATH/verify.sh" --github-output "$@")
printf '%s\n' "$out" >> "$GITHUB_OUTPUT"
# A mirror nobody could fetch covers nothing, silently in the gates —
# so say it where a fleet-wide slowdown would otherwise hide it.
# Two checks, not one case: both can be true, and each deserves its line.
case "$out" in
*"notes=unreachable"*)
echo "::warning title=attest::origin's notes could not be fetched; nothing was skipped (see the log above)" ;;
esac
case "$out" in
*"notes=unwritable"*)
echo "::warning title=attest::a stale lock keeps origin's notes out of the mirror; nothing was skipped (the log above says what to rm)" ;;
esac