Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/codeql/codeql-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2026 BitWise Media Group Ltd
# SPDX-License-Identifier: MIT

# CodeQL analysis configuration for this repo's own self-codeql.yaml run.
# CodeQL analysis configuration for this repo's own self-security.yaml run.
#
# Security only: the security-extended suite runs the security queries (a
# superset of the default) without the maintainability/reliability "quality"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#
# Runs via workflow_call from a thin caller (examples/ci.yaml) that owns the
# push/pull_request triggers.
name: Continuous integration
name: Reusable Continuous Integration

on:
workflow_call:
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/dependabot-merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
# review decision to APPROVED) — the same assumption as the `/merge` flow. See
# README.md for the one-time org setup (App, ruleset bypass, org var/secret); it
# is the same "FF Merge" App as merge.yaml.
name: Dependabot auto-merge
name: Reusable Dependabot Auto-Merge

on:
workflow_call:
Expand Down Expand Up @@ -122,11 +122,13 @@ jobs:
with:
client-id: ${{ inputs.app-client-id }}
private-key: ${{ secrets.app-private-key }}
# ff-merge moves the ref (contents) and reads the PR (pull-requests);
# the approval (minor/patch only) is the authorization gate, not
# ff-merge moves the ref (contents) and reads the PR (pull-requests), and
# needs workflows to move a ref whose commits touch .github/workflows/
# files; the approval (minor/patch only) is the authorization gate, not
# maintainer-only, so no administration scope is needed.
permission-contents: write
permission-pull-requests: write
permission-workflows: write

- name: Resolve the open Dependabot PR for this branch
id: pr
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/merge-notice.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# run's GITHUB_TOKEN, scoped to pull-requests:write. No App token or secret is
# needed — posting a comment is not a privileged operation.

name: Fast-forward merge notice
name: Reusable Merge Notice

on:
workflow_call:
Expand Down
42 changes: 25 additions & 17 deletions .github/workflows/merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
# require-approval:true, so a qualifying approval plus green checks is always required.
# Requires branch protection that requires PR review. See README.md for the one-time
# org setup (App, ruleset bypass, org var/secret).
name: Merge
name: Reusable Merge

on:
workflow_call:
Expand Down Expand Up @@ -89,9 +89,9 @@ jobs:
# action still re-verifies write access authoritatively, so this only filters
# noise before any token is minted.
if: >-
github.event_name == 'issue_comment' && github.event.issue.pull_request &&
github.event.comment.body == inputs.merge-command &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.comment.body ==
inputs.merge-command && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'),
github.event.comment.author_association)
runs-on: ubuntu-latest
concurrency:
group: ff-merge-${{ github.repository }}
Expand All @@ -105,11 +105,14 @@ jobs:
client-id: ${{ inputs.app-client-id }}
private-key: ${{ secrets.app-private-key }}
# Least-privilege within the App's installation ceiling: ff-merge moves the
# base ref (contents), reads/comments on the PR (pull-requests), and
# resolves the actor's access level for maintainer-only (administration).
# base ref (contents), reads/comments on the PR (pull-requests), resolves
# the actor's access level for maintainer-only (administration), and moves a
# ref whose commits touch .github/workflows/ files (workflows — GitHub
# rejects any ref update that adds or edits a workflow file without it).
permission-contents: write
permission-pull-requests: write
permission-administration: read
permission-workflows: write

- name: Fast-forward merge
uses: bitwise-media-group/ff-merge@76046d3da4a351eb1b1225727a3a7d0d178f1dfe # v1.1.0
Expand All @@ -128,12 +131,11 @@ jobs:
# triage+ requirement for labelling plus the approval gate below, and is scoped
# to same-repo PRs (a labelled fork PR has no secrets, so it arms by comment).
if: >-
(github.event_name == 'issue_comment' && github.event.issue.pull_request &&
github.event.comment.body == inputs.arm-command &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request' && github.event.action == 'labeled' &&
github.event.label.name == inputs.label &&
github.event.pull_request.head.repo.full_name == github.repository)
(github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.comment.body ==
inputs.arm-command && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'),
github.event.comment.author_association)) || (github.event_name == 'pull_request' && github.event.action ==
'labeled' && github.event.label.name == inputs.label && github.event.pull_request.head.repo.full_name ==
github.repository)
runs-on: ubuntu-latest
concurrency:
group: ff-merge-${{ github.repository }}
Expand All @@ -147,10 +149,12 @@ jobs:
client-id: ${{ inputs.app-client-id }}
private-key: ${{ secrets.app-private-key }}
# Labels and comments on the PR (pull-requests) and moves the ref via the
# best-effort ff-merge (contents); maintainer-only:false here, so no
# best-effort ff-merge (contents), which also needs workflows when the merge
# touches .github/workflows/ files; maintainer-only:false here, so no
# administration scope is needed.
permission-contents: write
permission-pull-requests: write
permission-workflows: write

- name: Resolve the pull request number
id: pr
Expand Down Expand Up @@ -233,10 +237,12 @@ jobs:
with:
client-id: ${{ inputs.app-client-id }}
private-key: ${{ secrets.app-private-key }}
# ff-merge moves the ref (contents) and reads the PR (pull-requests);
# maintainer-only:false here, so no administration scope is needed.
# ff-merge moves the ref (contents) and reads the PR (pull-requests), and
# needs workflows to move a ref whose commits touch .github/workflows/
# files; maintainer-only:false here, so no administration scope is needed.
permission-contents: write
permission-pull-requests: write
permission-workflows: write

- name: Resolve the open PR for this branch
id: pr
Expand Down Expand Up @@ -297,10 +303,12 @@ jobs:
with:
client-id: ${{ inputs.app-client-id }}
private-key: ${{ secrets.app-private-key }}
# ff-merge moves the ref (contents) and reads the PR (pull-requests);
# maintainer-only:false here, so no administration scope is needed.
# ff-merge moves the ref (contents) and reads the PR (pull-requests), and
# needs workflows to move a ref whose commits touch .github/workflows/
# files; maintainer-only:false here, so no administration scope is needed.
permission-contents: write
permission-pull-requests: write
permission-workflows: write

- name: Fast-forward merge
# Best effort, like the arm attempt: a decline (e.g. CI not green yet) must
Expand Down
14 changes: 8 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#
# Runs via workflow_call from a thin caller (examples/release.yaml) that owns the
# push trigger and grants the release permissions.
name: Release
name: Reusable Release

on:
workflow_call:
Expand Down Expand Up @@ -55,7 +55,11 @@ on:
needed if .goreleaser.yaml publishes a cask.
required: false

permissions: {} # granted per job
# Granted per job below. Note for callers: GitHub validates a reusable workflow's
# permissions as the union of every job and ignores `if:`, so a caller must grant
# the goreleaser job's id-token/attestations/artifact-metadata even on the publish
# path where that job is skipped -- see examples/release.yaml.
permissions: {}

# concurrent release-please runs can open duplicate release PRs; queue them.
concurrency:
Expand Down Expand Up @@ -133,8 +137,7 @@ jobs:
# not a denylist of fork-triggerable events: a new event type GitHub may add
# later fails closed (the job is skipped) until it is vetted and added here.
if: >-
needs.release-please.outputs.release_created &&
needs.release-please.outputs.goreleaser &&
needs.release-please.outputs.release_created && needs.release-please.outputs.goreleaser &&
contains(fromJSON('["push","workflow_dispatch","schedule"]'), github.event_name)
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -208,8 +211,7 @@ jobs:
# Same trusted-trigger allowlist as the goreleaser job above (fails closed on
# any event type not vetted here).
if: >-
needs.release-please.outputs.release_created &&
!needs.release-please.outputs.goreleaser &&
needs.release-please.outputs.release_created && !needs.release-please.outputs.goreleaser &&
contains(fromJSON('["push","workflow_dispatch","schedule"]'), github.event_name)
runs-on: ubuntu-latest
permissions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
# installs the toolchain matched to go-version-file (default go.mod), never the
# workflow.
#
# Runs via workflow_call from a thin caller (examples/codeql.yaml) that owns the
# Runs via workflow_call from a thin caller (examples/security.yaml) that owns the
# push/pull_request/schedule triggers and grants the analyze permissions.
name: CodeQL analysis
name: Reusable Security Analysis

on:
workflow_call:
Expand All @@ -42,9 +42,9 @@ on:
type: string
languages:
description:
Comma-separated CodeQL languages to analyse (e.g. "actions" or "actions,go"), overriding root
detection. Empty (default) auto-detects actions, plus go (root go.mod) and javascript-typescript
(package.json). Set this when detection would add an empty leg -- e.g. a tooling-only package.json.
Comma-separated CodeQL languages to analyse (e.g. "actions" or "actions,go"), overriding root detection. Empty
(default) auto-detects actions, plus go (root go.mod) and javascript-typescript (package.json). Set this when
detection would add an empty leg -- e.g. a tooling-only package.json.
required: false
default: ""
type: string
Expand Down Expand Up @@ -162,5 +162,7 @@ jobs:
# permission already granted to this job; in SARIF mode zizmor exits 0 even
# with findings, so this step never fails the run.
- name: Run zizmor
if: matrix.language == 'actions' && hashFiles('zizmor.yml', 'zizmor.yaml', '.github/zizmor.yml', '.github/zizmor.yaml') != ''
if:
matrix.language == 'actions' && hashFiles('zizmor.yml', 'zizmor.yaml', '.github/zizmor.yml',
'.github/zizmor.yaml') != ''
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
2 changes: 1 addition & 1 deletion .github/workflows/self-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# go.mod and no committed dist/ -- just a package.json for the prose toolchain --
# so the reusable workflow sets up node only and runs the canonical Makefile gates:
# lint (prettier --check + markdownlint), with build and test as no-ops here.
name: Continuous integration
name: Continuous Integration

on:
push:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/self-dependabot-merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@
#
# Both triggers are required: pull_request_target to approve on open, and
# workflow_run to fast-forward once CI finishes green. The workflow_run list names
# every workflow that must pass — "Continuous integration" (self-ci.yaml) and
# "CodeQL analysis" (self-codeql.yaml); whichever finishes last triggers the merge
# every workflow that must pass — "Continuous Integration" (self-ci.yaml) and
# "Security Analysis" (self-security.yaml); whichever finishes last triggers the merge
# attempt, and ff-merge verifies all checks before moving the ref.
#
# Org prerequisites (see bitwise-media-group/ff-merge): the FF_MERGE_CLIENT_ID
# variable + FF_MERGE_PRIVATE_KEY secret and the "FF Merge" App in main's ruleset
# bypass list — the same App as the /merge flow.
name: Dependabot auto-merge
name: Dependabot Auto-Merge

on: # zizmor: ignore[dangerous-triggers] -- required: approve needs base-repo secrets; no PR code is checked out or run
pull_request_target:
types: [opened, reopened, synchronize]
workflow_run:
workflows: ["Continuous integration", "CodeQL analysis"]
workflows: ["Continuous Integration", "Security Analysis"]
types: [completed]

# the app token does the privileged work; the caller grants nothing
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/self-merge-notice.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# reusable workflow only posts a static comment with a pull-requests:write token,
# so the elevated context is safe. No secret needed.

name: Merge notice
name: Merge Notice

on: # zizmor: ignore[dangerous-triggers] -- required: notice must reach fork PRs; only posts a static comment, no PR code is run
pull_request_target:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/self-merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
# All four triggers are routed by github.event_name inside merge.yaml. issue_comment
# runs in the base-repo context (so `/merge` and `/auto-merge` work even for fork
# PRs); the label-arm and review paths are same-repo only. The workflow_run list
# names every workflow that must pass — "Continuous integration" (self-ci.yaml) and
# "CodeQL analysis" (self-codeql.yaml); whichever finishes last triggers the attempt
# names every workflow that must pass — "Continuous Integration" (self-ci.yaml) and
# "Security Analysis" (self-security.yaml); whichever finishes last triggers the attempt
# and ff-merge re-verifies all checks.
#
# Org prerequisites (see bitwise-media-group/ff-merge): the FF_MERGE_CLIENT_ID
Expand All @@ -31,7 +31,7 @@ on: # zizmor: ignore[dangerous-triggers] -- required: workflow_run gates auto-me
pull_request_review:
types: [submitted]
workflow_run:
workflows: ["Continuous integration", "CodeQL analysis"]
workflows: ["Continuous Integration", "Security Analysis"]
types: [completed]

# the App token does the privileged work; the caller grants nothing
Expand Down
20 changes: 17 additions & 3 deletions .github/workflows/self-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,27 @@ on:
push:
branches: [main]

# release-please needs issues + pull-requests; the publish job moves the vanity
# tags with contents:write. No .goreleaser.yaml here, so the reusable workflow's
# goreleaser job is skipped and its id-token/attestations are not granted.
# Ceiling for the reusable workflow's release-please + publish jobs. GitHub
# resolves a reusable workflow's permissions statically at startup: it takes the
# union of every job's declared permissions and ignores `if:`. So the caller must
# grant what the goreleaser job declares (id-token / attestations /
# artifact-metadata) even though that job is skipped here (no .goreleaser.yaml) --
# dropping them startup_failures the whole run. No executed job realizes them at
# runtime: release-please and publish declare their own narrower job-level
# permissions, and the goreleaser job never runs.
permissions:
# release-please cuts the release/tag; publish moves the vanity tags
contents: write
# release-please creates pr labels via the issues api
issues: write
# release-please opens the release pull request
pull-requests: write
# cosign keyless signing
id-token: write
# github build-provenance attestation
attestations: write
# artifact storage record for the attestation
artifact-metadata: write

jobs:
release:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Copyright 2026 BitWise Media Group Ltd
# SPDX-License-Identifier: MIT

# This repo's own CodeQL run, dogfooding the reusable codeql workflow by local path
# This repo's own CodeQL run, dogfooding the reusable security workflow by local path
# (so it always runs the current definition, not a release pin). The library is
# GitHub Actions YAML + Markdown with no compilable Go and no JavaScript/TypeScript
# product source -- the package.json is prose-linting tooling only -- so it pins
# `languages: actions` rather than let detection add an empty javascript-typescript
# leg from that package.json. The actions leg also runs zizmor, gated on the root
# zizmor.yaml shipped here.
name: CodeQL analysis
name: Security Analysis

on:
push:
Expand All @@ -31,6 +31,6 @@ permissions:

jobs:
analyze:
uses: ./.github/workflows/codeql.yaml
uses: ./.github/workflows/security.yaml
with:
languages: actions
Loading
Loading