The devantler-tech organization's GitHub home: its public profile and community defaults, the
declarative configuration of the GitHub organization itself (deploy/), and the shared CI/CD
building blocks every product repository uses, documented below. Contributors and agents start with
AGENTS.md.
The shared CI/CD building blocks used across all DevantlerTech projects β both composite actions and reusable workflow_call workflows β in one repository. (The reusable workflows were merged in from devantler-tech/reusable-workflows; that repo is being retired and will be archived once all consumers migrate their uses: pins here.)
An action is a step inside one of your jobs. A reusable workflow replaces a whole job. Both are called by path from this repository, pinned to a full commit SHA:
jobs:
build:
runs-on: ubuntu-latest
steps:
# an action β a step in your own job
- uses: devantler-tech/.github/actions/setup-go-toolchain@<full-commit-sha> # vX.Y.Z
release:
# a reusable workflow β the whole job comes from here
uses: devantler-tech/.github/.github/workflows/create-release.yaml@<full-commit-sha> # vX.Y.Z
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}Replace <full-commit-sha> with the selected release's full 40-character commit SHA and vX.Y.Z with its version. The placeholders must be replaced before running these examples. Each entry in the tables below links to its own inputs and outputs.
The diagram below shows how GitHub Workflows, Jobs, Steps, Reusable Workflows, and Actions relate.
---
title: GitHub Actions Relationship Diagram
---
flowchart TD
A[Workflows] --> B[Jobs]
B --> C([Reusable Workflows])
B --> D[Steps]
C --> D
C --> B
D --> E[Actions]
E -.- F([Composite Actions])
F --> D
E -.- G([JavaScript Actions])
E -.- H([Docker Container Actions])
| Action | Description |
|---|---|
| aggregate-job-checks | Aggregate multiple job results into a single required check |
| approve-pr | Approve a PR using a GitHub App identity |
| cleanup-ghcr-packages | Clean up old GHCR packages |
| create-issues-from-todos | Create GitHub issues from TODO comments, with optional project integration |
| dependency-review | Scan a PR's dependency changes for vulnerabilities and disallowed licenses |
| diagnose-flux | Dump Flux reconcile state, controller logs, and failing pod logs on a stuck deploy |
| enable-auto-merge-on-pr | Enable auto-merge on a pull request |
| free-disk-space | Reclaim runner disk by removing large preinstalled toolchains |
| guard-installed-skill-edits | Refuse a hand-edit to a synced installed-skill tree, naming its upstream |
| login-to-ghcr | Login to GitHub Container Registry |
| run-dotnet-tests | Test .NET solution or project with coverage |
| setup-agent-skills | Install agent skills via gh skill from a newline list of <owner/repo> <skill>[@pin] entries, for one or more agents (e.g. Copilot, Claude Code) |
| setup-go-toolchain | Setup Go with private module discovery |
| setup-ksail-cli | Install KSail CLI via Homebrew |
| update-agent-skills | Run gh skill update --all against installed skills and report changes |
| upload-coverage | Upload a Cobertura coverage report to GitHub Code Quality |
| upsert-issue | Create, update, reopen, or close a GitHub issue by title |
| validate-naming | Configurable Kubernetes manifest and machine patch naming validation |
| validate-retired-repo-links | Catch links to retired GitHub repositories with documented historical exceptions |
| validate-shell-pipelines | Detect early-exit grep assertions that can invert results under pipefail |
These are not on the GitHub Marketplace, and that is deliberate: Marketplace does not list actions
that live in subdirectories, and everything here shares one repository and one release stream. Call
them by path as shown above. The reasoning, and when it would be worth revisiting, is in
AGENTS.md.
Reusable workflows are designed to encapsulate common CI/CD patterns that can be shared across multiple repositories. They allow you to define a workflow once and reuse it in the job-scope of other workflows. This reduces duplication and enables building generic workflows for common tasks.
world-at-ruin-required-regressions.yaml
is a target-specific GitHub ruleset workflow source, not a caller-facing reusable workflow. The
World at Ruin organization ruleset is managed declaratively by devantler-tech/.github and selects
this repository, path and refs/heads/main. provider-upjet-github v0.20.0 does not expose GitHub's
immutable workflow SHA selector, so reviewed Actions main is the strongest source binding the
deployed provider can express. github.workflow_sha still binds each individual run to the exact
Actions revision GitHub selected. At runtime the workflow checks out candidate product bytes at
github.sha, then separately checks out World at Ruin at GitHub's pull-request or merge-group base
SHA. Only that trusted base snapshot supplies client/tests,
tools/required-regression-control.sh, the test runner, and the aggregate verdict.
The workflow deliberately completes as a no-op for ordinary events in this source repository and
fails closed for every target other than devantler-tech/world-at-ruin. Keep the source workflow active:
the eligibility no-op is the declarative-safe source-repository behavior, while disabling it would
introduce unmanaged imperative state.
Change the control boundary in this order: merge an exact-head-reviewed source change here, confirm a
required-workflow run reports the merged github.workflow_sha, and verify live positive and negative
World at Ruin controls. A repository/path/ref change additionally goes through a reviewed .github
PR, released signed manifest bundle, reconciliation, and live ruleset read-back. Changes confined to
World at Ruin tests or its controller need no source-workflow change because each run takes those
bytes from the GitHub-supplied trusted base.
apply-signed-fixes.yaml commits a patch back to a
pull request branch as a signed commit. It is an internal building block, not a caller-facing
workflow: it expects a patch artifact uploaded earlier in the same run, so it does nothing useful on
its own.
Commits made with the git CLI on a runner are unsigned. A commit created through GitHub's commit API with an App token is signed by GitHub, so this workflow creates it that way and then reads the new commit's own verification object to prove it β an unsigned automation commit is what stops commit signature verification from being required on anything but the default branch.
The caller runs the fixer and exports its result as a patch; this workflow mints the write credential on a fresh runner and commits it. A fixer configured from the pull request under review can execute arbitrary commands, so it never runs in a job holding a write-scoped token. Callers gate on whether auto-fixing is enabled; everything visible from the pull request itself β event type, forks, and dependency-bot branches β is gated here, so a caller cannot omit it.
Whether a patch is eligible for automatic commitment is passed in as fixes-created (boolean,
default true) rather than used by the caller to skip the call. Committing raises synchronize,
which can start a replacement run and cancel the committing one before it verifies its own commit; the replacement
run's fixer then finds the work already done and reports no patch. The branch-tip signature check
therefore runs on every call, before the App token is minted and independently of fixes-created,
while every step that needs write access is skipped when it is false. A caller that omits the input
keeps its previous behaviour.
Both MegaLinter workflows keep the signer's token limited to file contents. Opt in with
manual-workflow-fixes: true to recover workflow-file edits manually. The option defaults to false,
preserving existing routing for callers that omit it. When enabled, a formatter change under the
repository's .github/workflows/ directory produces a warning instead of an automatic commit.
Runs eligible to upload fixes retain the complete patch as megalinter-fixes-<check-run-id>,
including when linting also reports an unfixable error. Cancelled runs do not force recovery.
Related edits and renames stay together; ordinary patches without workflow edits still receive
automatic signed commits.
Download and extract the artifact from the run, then apply its .patch file from the repository root
with git apply <file>.patch, review, and push. Successful lint runs still check the branch-tip
signature. Actual lint errors still fail the job and prevent signer writes;
validate-go-project's read-only mode still fails on uncommitted fixes.
Consumer rollout and flag removal are tracked in devantler-tech/actions#1186.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
artifact-name |
Input (string) | megalinter-fixes |
No | Uploaded artifact containing one <artifact-name>.patch file |
commit-message |
Input (string) | chore: Apply megalinter fixes |
No | Commit message for the applied patch |
pr-owner |
Input (string) | "" |
No | Pull request author login used to suppress commits to dependency-bot branches |
fixes-created |
Input (boolean) | true |
No | Whether a patch exists; false skips signing and retains the branch-tip signature check |
APP_PRIVATE_KEY |
Secret | - | For signing | GitHub App private key; paired with the APP_CLIENT_ID variable. No unsigned fallback |
Click to expand
.github/workflows/create-release.yaml is a workflow used to create releases using semantic-release.
The release is published with a GitHub App token, so the caller must set the APP_CLIENT_ID repository/organization variable alongside the APP_PRIVATE_KEY secret. The App always needs contents: write (tags/releases). By default it also needs issues: write + pull-requests: write for semantic-release success/fail hooks. Set disable-issue-side-effects: true to suppress those hooks and mint the token with contents: write only.
Npm alignment runs unconditionally. Release runs read requirements from the consumer's packageManager field and npm entries under devEngines.packageManager after checkout. An exact stable packageManager version is installed exactly; integrity-suffixed descriptors fail explicitly because this workflow cannot verify their digest. Otherwise, blocking devEngines entries must describe complete npm majors (11, 11.x, ^11.0.0, or >=11.0.0 <12.0.0). Arrays are alternatives: versionless npm entries satisfy the contract, and when no entry matches the final alternative's onFail controls the result. Without an exact npm packageManager requirement, warn and ignore keep the current npm; error and download align to a supported complete-major alternative or fail. Compatible declarations resolve to the exact packageManager version, consumers with neither an exact npm packageManager requirement nor a blocking npm devEngines version keep the bundled npm, and explicit nulls, unknown properties, malformed, contradictory, prerelease, or otherwise unenforceable contracts fail explicitly.
When adopting this breaking release, remove the retired align-npm-with-consumer-contract argument. Previously published canonical revisions and the frozen devantler-tech/actions catalogue retain their existing APIs and behavior. The frozen family's disposition remains tracked in #392; retiring this canonical input does not remove that legacy input.
Release runs for one repository and ref run one at a time, in the order they were queued, and waiting runs are kept (up to GitHub's limit of 100) rather than cancelled. Two merges that land close together therefore produce two sequential release runs instead of racing for the same version. Callers need no concurrency block of their own.
Consumers that maintain explicit type!: breaking-change handling can set warn-missing-breaking-bang: true to catch accidental removal. Before releasing, the workflow warns when an explicitly listed @semantic-release/commit-analyzer has no nonempty parserOpts.breakingHeaderPattern. The check reads JSON from .releaserc, .releaserc.json, or the release key in package.json; it never changes files or blocks a release. The default is off, so consumers that have not adopted this convention get no warning noise.
This is a narrow configuration check, not proof that a commit will produce a major release. Default plugins, shared configurations, custom parser configs, presets (including conventionalcommits, which may provide their own handling), non-JSON files, and multiple competing config files are left to semantic-release. The guard does not execute configuration or infer which file wins. When restoring a lost parser setting, also verify that the consumer's release rules select a major version for breaking changes.
Consumer rollout and the decision on removing this temporary flag are tracked in devantler-tech/actions#1347.
For catalogue self-tests, set offline-test: true together with dry-run: true and omit the App secret. This independent job requests no repository permissions and retrieves its immutable public catalogue source anonymously. It evaluates the pinned release command and catalogue configuration against disposable local repositories, verifying patch, minor, major, and no-release decisions without changing files or refs. It also honors warn-missing-breaking-bang: a missing-pattern fixture emits exactly one warning when enabled, while intact, disabled, unsupported-config and tool-error cases continue without warning or release blockage. Both issue-hook settings are exercised; this does not test live notification delivery. Normal releases and consumer dry-run previews continue to use the consumer's own configuration and App credentials, including callers that declare permissions: {}.
jobs:
release:
uses: devantler-tech/.github/.github/workflows/create-release.yaml@<full-commit-sha> # vX.Y.Z
with:
disable-issue-side-effects: true
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_CLIENT_ID |
Variable | - | Yes | GitHub App client ID used to mint the release token |
APP_PRIVATE_KEY |
Secret | - | No | GitHub App private key; required for consumer releases and previews, omitted for offline tests |
disable-issue-side-effects |
Input (boolean) | false |
No | Disable success/fail hooks and omit issue/pull-request token permissions |
warn-missing-breaking-bang |
Input (boolean) | false |
No | Warn about missing explicit breaking-header handling in supported JSON configurations |
dry-run |
Input (boolean) | false |
No | Run semantic-release in dry-run mode (no tags or publishes) |
offline-test |
Input (boolean) | false |
No | Run secret-free catalogue release-decision fixtures; requires dry-run |
Click to expand
.github/workflows/delete-workflow-runs.yaml is a workflow used to clean up old workflow runs from a repository.
jobs:
delete-runs:
uses: devantler-tech/.github/.github/workflows/delete-workflow-runs.yaml@<full-commit-sha> # vX.Y.Z
permissions:
actions: write
contents: read
with:
days: 30 # optional
minimum-runs: 6 # optional
dry-run: false # required to perform actual deletions (defaults to true)| Key | Type | Default | Required | Description |
|---|---|---|---|---|
repository |
Input (string) | Calling repo | No | Repository to target for workflow run deletion |
days |
Input (number) | 30 |
No | Days-worth of runs to keep for each workflow |
minimum-runs |
Input (number) | 6 |
No | Minimum runs to keep for each workflow |
delete-workflow-pattern |
Input (string) | - | No | Name or filename of the workflow to target |
delete-workflow-by-state-pattern |
Input (string) | ALL |
No | Filter workflows by state (comma or pipe separated) |
delete-run-by-conclusion-pattern |
Input (string) | ALL |
No | Remove runs by conclusion (comma or pipe separated) |
dry-run |
Input (boolean) | true |
No | Logs simulated changes, no deletions are performed |
Note: The calling workflow must grant
actions: writeandcontents: readpermissions.
Cleanup uses a Go driver from the reusable workflow's exact commit. It first lists
all workflows and runs, then applies the age, minimum-run and conclusion filters.
Every workflow's retention decision uses that same complete repository-run snapshot,
avoiding repeated scans of the same history through separate workflow endpoints.
Run enumeration ends at the last complete second before cleanup starts, so new
runs wait for the next cleanup instead of shifting pagination. Searches reaching
GitHub's 1,000-result limit are split into disjoint creation-time ranges; a range
that still reaches the limit within one second fails rather than skipping history.
The newest minimum-runs matching old runs are retained in addition to recent runs.
Runs whose workflow is no longer listed are selected independently of those filters
only after an individual workflow lookup confirms absence. Workflows that appear
during enumeration are retained; an uncertain lookup fails before deletion.
Workflow patterns match names or filenames
without case sensitivity. Workflow, state and conclusion filters accept comma or
pipe separated values.
Invalid or incomplete API responses fail before deletion starts. Transient read
failures receive at most two retries; a deletion is attempted once, requires HTTP
204 confirmation, and stops cleanup on rejection or an unknown outcome. Confirmed
deletions are spaced by at least one second; cancellation interrupts that pause.
Previews do not wait between selected runs. A rerun
lists the current history again. days must be finite and nonnegative;
minimum-runs must be a nonnegative integer.
.github/workflows/delete-workflow-runs-readonly.yaml
executes the same cleanup driver with actions: read and contents: read only.
Use it for previews and catalogue tests that must have no authority to delete workflow history.
Deletion requires the production entrypoint above and an explicit dry-run: false.
The read-only entrypoint is generated from the complete production wrapper by
bash .github/scripts/generate-cleanup-readonly.sh. Required CI checks preserve its
input behavior and source parity. Hosted dry-runs prove execution and the credential
boundary. Native HTTP fixtures exercise retention, pagination, filters, repository
selection, command exit codes and rejected or unconfirmed deletion without live
deletion authority.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
repository |
Input (string) | Calling repo | No | Repository whose workflow runs are previewed |
days |
Input (number) | 30 |
No | Days-worth of runs to retain |
minimum-runs |
Input (number) | 6 |
No | Minimum runs to retain per workflow |
delete-workflow-pattern |
Input (string) | - | No | Workflow name or filename to match |
delete-workflow-by-state-pattern |
Input (string) | ALL |
No | Comma or pipe separated workflow state filters |
delete-run-by-conclusion-pattern |
Input (string) | ALL |
No | Comma or pipe separated run conclusion filters |
dry-run |
Input (boolean) | true |
No | Log proposed deletions; a false value still cannot grant deletion authority |
Click to expand
.github/workflows/dependency-review.yaml scans a pull request's dependency changes for known-vulnerable packages and disallowed licenses using GitHub Dependency Review. It is designed to run as an organization Required Workflow as well as via workflow_call.
It is non-blocking by default (warn-only: true, fail-on-severity: critical) so it can be required org-wide without blocking existing PRs; set warn-only: false to enforce. With comment-summary-in-pr: never (the default) the job needs only contents: read.
jobs:
dependency-review:
uses: devantler-tech/.github/.github/workflows/dependency-review.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read| Name | Description | Default |
|---|---|---|
fail-on-severity |
Block on vulnerabilities of this severity or higher (low, moderate, high, critical); only when warn-only is false. |
critical |
fail-on-scopes |
Comma-separated scopes to block on (runtime, development, unknown). |
runtime |
allow-licenses |
Comma-separated SPDX allow-list (empty = not enforced). Mutually exclusive with deny-licenses. |
"" |
deny-licenses |
Comma-separated SPDX deny-list (empty = not enforced). Mutually exclusive with allow-licenses. |
"" |
comment-summary-in-pr |
Post the summary as a PR comment (always, on-failure, never); anything but never requires the repo-token secret below. |
never |
warn-only |
Report findings as warnings and always succeed (non-blocking); set false to enforce. |
true |
| Name | Description | Required |
|---|---|---|
repo-token |
Repository-scoped token with Contents: read and Pull requests: write. Required for always and on-failure; ignored for never. |
Only when posting comments |
The reusable workflow always keeps its job's GITHUB_TOKEN at contents: read.
Granting the calling job pull-requests: write alone cannot enable comments:
reusable workflows can only retain or reduce the caller's token permissions.
Pass an independent token through the named secret instead. Prefer a short-lived
GitHub App installation token
limited to the target repository and the two permissions above; a fine-grained token
with those permissions also works. Do not pass the calling job's GITHUB_TOKEN as a
substitute for this independent credential.
jobs:
dependency-review:
uses: devantler-tech/.github/.github/workflows/dependency-review.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
with:
comment-summary-in-pr: always # or on-failure
secrets:
repo-token: ${{ secrets.DEPENDENCY_REVIEW_COMMENT_TOKEN }}A missing token fails with a specific setup message before dependency review starts; the API enforces the supplied token's actual permissions. Secretless callers and organization-required runs retain the comments-off default. Secrets are unavailable to fork pull requests, so use the default there and enable comments only in trusted caller contexts where the independent credential is available.
Click to expand
.github/workflows/deploy-github-pages.yaml is a workflow used to build and deploy a Jekyll site to GitHub Pages.
jobs:
pages:
uses: devantler-tech/.github/.github/workflows/deploy-github-pages.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
pages: write
id-token: write
with:
ruby-version: "3.3" # optional
jekyll-env: production # optional
extra-build-args: "" # optional, e.g. '--future'
working-directory: "." # optional, e.g. 'docs' if Jekyll site is in a subdirectory| Key | Type | Default | Required | Description |
|---|---|---|---|---|
dry-run |
Input (boolean) | false |
No | Skip build and deploy (validate workflow interface only) |
ruby-version |
Input (string) | 3.3 |
No | Ruby version to install |
jekyll-env |
Input (string) | production |
No | Jekyll environment |
extra-build-args |
Input (string) | "" |
No | Extra args appended before the automatically supplied --baseurl |
working-directory |
Input (string) | "." |
No | Working directory for the Jekyll site (e.g., 'docs') |
| Key | Description |
|---|---|
page-url |
Deployed Pages site URL |
Click to expand
.github/workflows/enable-auto-merge.yaml approves pull requests opened by an allowlist of trusted single-author bots, so routine bot PRs do not need a human click.
Use dry-run: true to exercise the workflow without changing a pull request.
This path runs offline decision fixtures with contents: read, forwards no App
secret, and has a separate concurrency lane so it cannot cancel live evaluations.
The catalogue tests use it for default, actor-trust and pending-queue calls.
Caller permissions still declare the production workflow's static write ceiling,
which GitHub validates even when those jobs are skipped; executed dry-run jobs
receive only read access. dry-run defaults to false, and live mutations require
APP_PRIVATE_KEY.
Cursor Automation PRs are eligible by the exact cursor[bot] author identity (app/cursor in the
GitHub UI). This author check reads the pull request author; a cursor[bot] Bugbot comment or review
does not count as trusted review evidence.
By default it behaves as it always has: it approves the PR and arms auto-merge, bound to the commit it checked.
With actor-trust enforcement turned on β the enforce-actor-trust input, or the
ENFORCE_ACTOR_TRUST repository/organization variable β every privileged pull-request, review,
or comment trigger on an allowlisted bot-authored PR must come from an allowlisted bot or the
explicit devantler maintainer actor. Workflow reruns also require the initiating
github.triggering_actor to be allowlisted; a trusted original event cannot be replayed by an
untrusted collaborator. A rejected pull-request lifecycle triggerβor, when review gates are also
enforced, an untrusted dismissal/deletion of trusted review evidenceβreceives no App token and
actively revokes both classic auto-merge and merge-queue state with the caller's GITHUB_TOKEN.
These state-removal runs arbitrate within each caller workflow at run creation, so they cancel an
older privileged run before its jobs can mutate the PR without cancelling another caller's run.
This caller-keyed state lease is acquired only when the reusable auto-merge lane actually executes,
so unrelated or conditionally skipped runs of the surrounding caller workflow cannot suppress
arming. Same-second lifecycle events share the lease, while unrelated title, label, or assignment
edits create no replacement run. Before mutation, a live reopen/ready-for-review/force-push timeline
binding rejects later events (including a head that moves away and returns) and treats any equal-time
sequence it cannot uniquely identify as superseded. Arming and revocation share one mutation lane;
a delayed rejected event preserves only auto-merge state whose authorization time is provably newer
than that rejected run attempt. Default-off review/comment no-ops skip the mutation lane entirely, so
they cannot replace a queued revocation.
Every workflow_call invocation that enables actor trust should provide a globally caller-unique,
ref-independent concurrency-key. Existing opted-in callers that omit it retain compatibility in a
safe repository-wide actor-trust lane; explicit keys isolate independent callers from one another.
The direct and cross-repository required-workflow paths have a built-in stable source identity.
Legacy default-off callers retain their existing authorization behavior.
Set queue-pending-evaluations: true on a workflow_call invocation to retain up to 100 pending
evaluations in non-cancelling workflow runs and both approval/revocation jobs. This temporary opt-in
defaults to false: omitted/false inputs keep the existing single queue, which can replace a waiting
evaluation.
Direct and organization-required runs take no inputs, so they opt in with the
QUEUE_PENDING_EVALUATIONS repository or organization variable set to true. Without it they keep
the single queue. The variable applies only to those runs: a workflow_call invocation always follows
its own input, so an explicit false stays honoured. Rollout and flag removal are tracked in
devantler-tech/actions#1155.
When opted in, GitHub processes queued evaluations in the order they begin waiting, which can
differ from event delivery order; evaluations still read current PR state. Actor-enforced lifecycle
and evidence-removal events keep their intentional workflow-level cancellation of stale runs, using
the compatible single queue. Additional arrivals beyond GitHub's 100-pending limit are cancelled.
The catalogue's queue test checks that all three queued jobs complete successfully in the same run attempt. Its observer retries transient HTTP server failures with bounded backoff and discards failed responses. Missing pages, duplicate jobs, stale attempts and unsuccessful slots fail the check; use Re-run all jobs to repeat the burst after a failure.
With review enforcement turned on β the enforce-review-gates input, or the
ENFORCE_MERGE_GATES repository/organization variable β it additionally requires, on the PR's
current commit, both a passing review (CodeRabbit approved, or a clean Codex pass when CodeRabbit
has no blocking result) and a fresh CodeRabbit pre-merge result. This gate fails closed: evidence
that is missing, stale, edited, mixed, or unreadable counts as a failure, and the workflow then
actively withdraws any auto-merge or merge-queue state the PR had.
Enforced runs approve but deliberately never arm the merge themselves. GitHub offers no way to tie mutable review evidence to the merge call atomically, so arming is left to whatever drives the merge afterwards.
[!IMPORTANT] If you enable enforcement via
workflow_call, addpull_request_reviewandissue_commenttriggers to your own caller workflow. Review results arrive after thepull_requestevent, and a reusable workflow cannot add triggers to the workflow calling it β so without these the gate never re-evaluates once a review lands.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
# Required when enforce-review-gates is true: review results land after
# the pull_request events, so the caller must re-invoke the gate on them
# β including edits and dismissals, which can turn green evidence red and
# must be able to DISARM, and deleted comments, since removing a pre-merge
# summary or Codex clean pass is evidence deletion the gate must re-evaluate.
pull_request_review:
types: [submitted, edited, dismissed]
issue_comment:
types: [created, edited, deleted]
jobs:
auto-merge:
uses: devantler-tech/.github/.github/workflows/enable-auto-merge.yaml@<full-commit-sha> # vX.Y.Z
permissions:
actions: read
pull-requests: write
contents: write
with:
enforce-actor-trust: false # default; enable after validating trusted trigger actors
enforce-review-gates: false # default; flip after the repo's review lanes are validated
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}Actor-trust note: Callers that enable actor trust must grant
actions: read,contents: write, andpull-requests: writeas shown above and should pass a stable, globally caller-uniqueconcurrency-key. An omitted key uses a safe repository-wide compatibility lane, which can cancel independent actor-trust call sites for the same PR but cannot let stale authority proceed; explicit keys avoid that availability tradeoff. Workflow-level arbitration orders lifecycle state only for the reusable lane that actually executes; skipped or unrelated caller runs do not reauthorize or supersede it. Both the original and rerun-triggering actors must be trusted for every privileged event. The read scope binds stale-revocation decisions to the rejected run attempt's durable start time; the write scopes revoke classic auto-merge or merge-queue state. Rejected events never receive the App private key. Missing lookup or revoke authority fails the required workflow closed.Note: The caller grants the documented minimum above with or without enforcement. Actor trust uses its caller
Actions: readscope only to bind stale revocation to the rejected run attempt's start time. Review enforcement uses a separate App token and additionally requires the GitHub App installation to grant Actions: read, Checks: read, and Contents: read. If a required scope is missing, the workflow fails closed rather than approving or arming on unproven evidence.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | No | GitHub App private key; required for live mutations, omitted for dry-run |
dry-run |
Input | false |
No | Run read-only offline decision fixtures without changing a pull request |
enforce-actor-trust |
Input | false |
No | Opt-in trusted-trigger enforcement with fail-closed revocation |
enforce-review-gates |
Input | false |
No | Opt-in fail-closed gate before approval; agent arms after live pentad |
queue-pending-evaluations |
Input | false |
No | Temporary opt-in to retain pending evaluations during event bursts |
QUEUE_PENDING_EVALUATIONS |
Variable | unset | No | The same temporary opt-in for direct and organization-required runs only |
concurrency-key |
Input | "" |
No | Recommended with actor trust; omitted calls share a safe fallback lane |
Click to expand
.github/workflows/lint.yaml lints a whole repository with MegaLinter (Go flavor), auto-fixing what it can and committing the result back to the pull request.
Which one do I want? If your repository is a Go module, use β Validate Go Project β it already runs MegaLinter as one stage of a full Go pipeline. Reach for this workflow when Go is only part of a larger tree (a game client plus a Go server, for example): it lints everything and leaves the Go build and tests to your own job.
The Go flavor covers Go, JSON, Markdown, YAML, shell, GitHub Actions, Dockerfile, Kubernetes, spelling, secrets and copy-paste. It has no GDScript, Python or Terraform linters β see MegaLinter's flavor list if you need those.
Configure the linters themselves in a .mega-linter.yml at your repository root, as usual.
MegaLinter always runs read-only, without a GitHub token or persisted checkout credentials. When auto-fixing is enabled, the lint job exports a patch and a separate job on a fresh runner mints the write-scoped App token, applies that patch, and commits it. This isolation prevents pull-request-controlled MegaLinter configuration from accessing repository write credentials.
Fork pull requests lint read-only, automatically. GitHub withholds secrets from forks, so fixes could never be committed back; auto-fixing there would only produce a failure an outside contributor cannot resolve. Real lint errors still fail on forks β only the auto-fix half is skipped.
Signed fixer branch policy: retain the existing dependency-only exclusion.
Both the event author and pr-owner are checked against dependabot[bot],
dependabot, renovate[bot], renovatebot and renovate. Other same-repository
automation, including release automation, github-actions[bot], ksail-bot
and botantler-1[bot], remains eligible when fixes are enabled. This preserves
existing consumers' behavior and keeps one policy across the signer and Go exporters.
The signing API uses the expected head to reject a concurrent branch change.
An automation's later refresh can still replace a fixer commit; that tradeoff is
accepted, and the branch's automation remains responsible for its next update.
jobs:
lint:
uses: devantler-tech/.github/.github/workflows/lint.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
with:
apply-fixes: falseTo enable pull-request auto-fixes, set apply-fixes: true and pass
APP_PRIVATE_KEY. MegaLinter still runs without that secret; only the separate
patch-application job can access it.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | No | GitHub App private key. Needed only to commit auto-fixes; without it a fixable finding fails the build instead |
working-directory |
Input | "" |
No | Directory to lint. Empty lints the whole repository |
go-version-file |
Input | "" |
No | Path to a go.mod. When set, Go is installed first so the Go linters use the module's toolchain, not the container's |
apply-fixes |
Input (boolean) | true |
No | Auto-fix and commit back to the pull request. Set false for a read-only gate |
manual-workflow-fixes |
Input (boolean) | false |
No | Opt in to complete workflow-file patches for manual application, including after lint errors; existing upload eligibility still applies |
pr-owner |
Input | "" |
No | Pull request author login. Auto-fix commits are suppressed for dependency-bot pull requests |
Click to expand
.github/workflows/publish-app.yaml builds and publishes a containerized app and its Kubernetes manifests to GHCR as cosign-signed OCI artifacts. Normal publication first requires authenticated evidence that the normalized version is absent from both repositories. The default mode builds under a run-specific staging tag, pins the produced image digest into the deployment manifest, stages the manifests artifact, and signs both produced digests. After both signatures succeed and an image copy preserves the signed digest, another authenticated absence check precedes promotion of the version and source-SHA image tags and manifests version. Stable releases then move both latest tags from those exact digests; prereleases preserve stable latest. Signing failures leave version and latest tags unchanged. Opt-in signed promotion below also verifies signature identity and signed publication claims before exposing release tags.
on:
push:
tags:
- "v*"
jobs:
publish-app:
uses: devantler-tech/.github/.github/workflows/publish-app.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read # checkout
packages: write # push image + manifests OCI artifact
id-token: write # keyless cosign signing
with:
app-name: my-app # container name in deploy/deployment.yaml
deploy-path: ./deploy # optionalNote: Must be invoked from a complete semantic-version tag (
vMAJOR.MINOR.PATCH, optionally with-PRERELEASEand+BUILD) β Docker semver tagging and FluxOCIRepositorysemver selection depend on it. Anything else, such asv1.2.3garbageor a version whose numbers exceed 15 digits, is refused before publishing; build metadata is dropped from the published version because an OCI tag cannot carry+. Theapp-namecontainer check ondeploy-path/deployment.yamlalso runs before anything is pushed, so a bad manifest leaves the registry untouched. The calling job must grantpackages: writeandid-token: write(andcontents: readfor checkout); no secrets are required (auth uses the GHCR-scopedGITHUB_TOKEN).
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
app-name |
Input (string) | - | Yes | Container name in the deployment manifest to pin to the built image digest |
deploy-path |
Input (string) | ./deploy |
No | Path to the Kubernetes manifests directory packaged as the OCI artifact |
dry-run |
Input (boolean) | false |
No | Skip publication and validate only the workflow interface |
enable-caller-pin |
Input (boolean) | true |
No | Require a 40-character commit-SHA caller before signing. The authenticated OIDC calling ref is the certificate identity. Explicit false remains supported during caller cleanup in #284 |
enable-signed-promotion |
Input (boolean) | false |
No | Verify both signature identities and paired publication claims before exposing version tags, then move latest only for stable releases. Requires enable-caller-pin; rollout is tracked in #371 |
enable-signed-recovery |
Input (boolean) | false |
No | Restore missing version tags from the original signed pair; requires signed promotion and caller pinning, and preserves both latest tags |
recovery-image-digest |
Input (string) | empty | No | Original signed image sha256 digest, required for recovery |
recovery-manifests-digest |
Input (string) | empty | No | Original signed manifests sha256 digest, required for recovery |
recovery-run-id |
Input (string) | empty | No | Original publication run ID, required for recovery |
recovery-run-attempt |
Input (string) | empty | No | Original publication attempt, required for recovery |
recovery-workflow-sha |
Input (string) | empty | No | Original publisher commit in this catalogue repository, required for recovery |
Opt in with both enable-signed-promotion: true and enable-caller-pin: true to stage the image and manifests under run-specific tags. Authenticated registry reads must establish that the normalized version is absent from both repositories before staging, and again before promotion. Existing versions and failed or ambiguous reads stop publication. Both signatures bind the publishing repository, source revision and tag, normalized version, run and attempt, immutable workflow identity, and the exact image/manifests digest pair. Both exact digests must pass signing, identity and claim verification before version tags are exposed; prereleases never move latest. Image promotion preserves the verified manifest format and checks its produced digest. Default, opted-in and recovery publishers for the same target in one caller repository wait in a shared queue without canceling pending releases. Registry tag writes are sequential; other writers can race the final absence check, and a promotion failure can leave only one version or latest tag updated. Production caller adoption remains in #371 and #242.
For an interrupted version publication, run explicit recovery on the original semantic-version tag and source revision. Enable signed recovery, signed promotion and caller pinning, and supply both original digests, the original run ID/attempt and publisher SHA. Both signatures must authenticate the same source, version, run and digest pair before either version is read. Both reads must prove absence or match the original bytes before any write. Recovery restores only missing version tags, performs no build or signing, and always preserves both latest tags. A matching retry makes no writes; a contradictory version or incomplete observation stops recovery. Image tagging preserves the signed manifest format and verifies the complete registry bytes. The two writes are not atomic: an interruption can leave one version present, and readback detects external races without preventing them. Retry with the same original pair. This accepts only the original catalogue repository identity; #242 governs migration from another publisher repository. Disposable native CI proves real registry tagging and local-key signature claims; it does not establish production OIDC or authorize rollout.
Click to expand
.github/workflows/publish-manifests.yaml publishes a Kubernetes manifests directory to GHCR as a cosign-signed OCI artifact, without building a container image. Normal publication first requires authenticated evidence that the normalized version is absent. The default mode stages the Flux-compatible artifact (ghcr.io/<owner>/<repo>/manifests) under a run-specific tag and signs the produced digest with keyless cosign (Fulcio/Rekor via GitHub OIDC). Another authenticated absence check precedes promotion of the version tag from that signed digest. Stable releases then move latest; prereleases preserve stable latest. Signing failures leave both release tags unchanged. Opt-in signed promotion below also verifies signature identity and signed publication claims before exposing the version. This is the manifests-only sibling of publish-app.yaml.
Because the signing happens inside this reusable workflow, the cosign certificate identity (OIDC subject) is this workflow's path β https://github.com/devantler-tech/.github/.github/workflows/publish-manifests.yaml@<ref> β not the caller's. Verifiers (e.g. a Flux OCIRepository verify.matchOIDCIdentity) must match that.
Opt in with both enable-signed-promotion: true and enable-caller-pin: true to verify the staged artifact's signature and publication claims before promotion. Authenticated registry reads must establish version absence before staging and again before promotion; existing versions and failed or ambiguous reads stop publication. The signed payload binds the publishing repository, source revision and tag, normalized version, run and attempt, and immutable workflow identity. The workflow signs and verifies the produced digest and these claims against its exact SHA-pinned OIDC identity before exposing the version tag; only stable releases then move latest. Failed signing or verification leaves both consumer-selectable tags unchanged, although the staging artifact remains. Default, opted-in and recovery publishers for the same target in one caller repository wait in a shared queue without canceling pending releases. Registry tag writes are sequential and cannot prevent another writer racing the final check. Consumer adoption of verified publication remains tracked in #371.
on:
push:
tags:
- "v*"
jobs:
publish-manifests:
uses: devantler-tech/.github/.github/workflows/publish-manifests.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read # checkout
packages: write # push manifests OCI artifact
id-token: write # keyless cosign signing
with:
oci-name: devantler-tech/github-config # optional override; defaults to github.repository
deploy-path: ./deploy # optionalNote: Must be invoked from a complete semantic-version tag (
vMAJOR.MINOR.PATCH, optionally with-PRERELEASEand+BUILD) β FluxOCIRepositorysemver selection depends on it. Anything else, such asv1.2.3garbageor a version whose numbers exceed 15 digits, is refused before publishing; build metadata is dropped from the published version because an OCI tag cannot carry+. The calling job must grantpackages: writeandid-token: write(andcontents: readfor checkout); no secrets are required (auth uses the GHCR-scopedGITHUB_TOKEN). Overrideoci-namewhen the repo name is an invalid OCI path component (e.g..githubβdevantler-tech/github-config).
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
oci-name |
Input (string) | ${{ github.repository }} |
No | OCI repository name (<owner>/<name>) the artifact is published under, without the registry prefix or trailing /manifests. Override for invalid OCI path components |
deploy-path |
Input (string) | ./deploy |
No | Path to the Kubernetes manifests directory packaged as the OCI artifact |
dry-run |
Input (boolean) | false |
No | Skip publication and validate only the workflow interface |
enable-caller-pin |
Input (boolean) | true |
No | Require a 40-character commit-SHA caller before signing. The authenticated OIDC calling ref is the certificate identity. Explicit false remains supported during caller cleanup in #284 |
enable-signed-promotion |
Input (boolean) | false |
No | Verify signature identity and publication claims before publishing version and stable latest tags. Requires enable-caller-pin; rollout and retirement are tracked in #371 |
enable-signed-recovery |
Input (boolean) | false |
No | Request restoration of only the original signed version tag; requires signed promotion and caller pinning. Never moves latest; rollout is tracked in #425 |
recovery-digest |
Input (string) | empty | No | Original signed sha256 manifests digest; required for recovery |
recovery-run-id |
Input (string) | empty | No | Original publication run ID; required for recovery |
recovery-run-attempt |
Input (string) | empty | No | Original publication attempt; required for recovery |
recovery-workflow-sha |
Input (string) | empty | No | Original publisher's full commit SHA in this catalogue repository; required for recovery |
Explicit recovery runs on the original semantic-version tag and source revision. Enable enable-signed-recovery, enable-signed-promotion and enable-caller-pin, and supply the original digest, run ID, attempt and publisher SHA. The workflow verifies the original signature identity and signed repository, source, version and run claims before reading the registry. A matching version succeeds without writing; only an authenticated, unambiguous missing version is restored from that digest. Contradictory versions and failed or partial reads stop recovery. Recovery never rebuilds or re-signs an artifact and always preserves latest, including for stable versions and prereleases. Original publishers from another repository identity are not accepted; that consumer migration remains in #242. The shared publisher queue serializes cooperating callers within a repository. Registry V2 has no compare-and-set operation, so readback detects a conflicting external writer but cannot prevent its race.
Click to expand
.github/workflows/publish-dotnet-library.yaml is a workflow used to publish .NET libraries to NuGet and GHCR.
jobs:
publish-library:
uses: devantler-tech/.github/.github/workflows/publish-dotnet-library.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
packages: write
secrets:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}| Key | Type | Default | Required | Description |
|---|---|---|---|---|
NUGET_API_KEY |
Secret | - | No | NuGet API key (required when dry-run is false) |
dry-run |
Input (boolean) | false |
No | Skip publish (validate workflow interface only) |
Click to expand
.github/workflows/run-dotnet-tests.yaml is a workflow used to test .NET solutions or projects across multiple operating systems. On authenticated runs, coverage is merged into a single Cobertura report and uploaded to GitHub Code Quality (native PR coverage).
jobs:
dotnet-test:
uses: devantler-tech/.github/.github/workflows/run-dotnet-tests.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
packages: read
code-quality: write # required for GitHub Code Quality coverage uploadNote: The calling workflow must grant
code-quality: write(otherwise the run fails at startup). Coverage requires the repo's Code Quality to be enabled (Settings β Code quality) and is skipped on credential-free pull-request runs.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
enable-github-packages |
Input (boolean) | false |
No | Use GITHUB_TOKEN for private packages and Code Quality on trusted same-repository non-bot pull-request code |
working-directory |
Input (string) | "" |
No | Directory containing the .NET solution or project |
Pull-request runs are credential-free by default. A trusted same-repository human-authored
pull request that needs private GitHub Packages can set enable-github-packages: true.
The workflow ignores that input for fork and bot pull requests, so their code cannot opt
itself back into the token-bearing path. Merge-group and direct non-pull-request runs
authenticate with the automatic GITHUB_TOKEN when the calling job grants packages: read.
The same explicit token boundary keeps the Code Quality uploader out of credential-free runs.
Click to expand
.github/workflows/scan-for-todo-comments.yaml is a workflow used to scan for TODOs in code and create GitHub issues.
jobs:
todos:
uses: devantler-tech/.github/.github/workflows/scan-for-todo-comments.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
issues: write
with:
ignore: "^third_party/"
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_CLIENT_ID |
Variable | - | No | Required for production project integration; unused in dry-run |
APP_PRIVATE_KEY |
Secret | - | No | Required for production project integration; unused in dry-run |
dry-run |
Input (boolean) | false |
No | Exercise the action wrapper offline without creating issues |
ignore |
Input (string) | "" |
No | Regular expression matching repository-relative paths to ignore |
exclude-vendored |
Input (boolean) | false |
No | Exclude root vendor directories when ignore is empty |
optional-project-auth |
Input (boolean) | false |
No | Opt in to project integration being optional |
project |
Input (string) | organization/devantler-tech/5 |
No | Project selection; explicitly empty omits integration when opted in |
With dry-run: true, omit the App secret: an executed job uses only a contents-read token and a
fail-closed Docker fixture to verify exactly one wrapper invocation and input forwarding. This
does not scan source comments; catalogue CI separately exercises the real scanner image.
The scanner fixtures verify complete requests and results
for healthy runs and API failures under the owned supervisor. Incomplete reads and failed issue or
project operations fail the action and stop later writes. The scanner runs once; image acquisition
and initial unauthenticated language-rule reads can retry before mutation.
The calling job still needs the static issues: write ceiling because GitHub validates the
production job even when it is skipped. Production uses the workflow token for issues and the
App token for the organization project.
Production callers can pass optional-project-auth: true and project: "" to omit
project integration. Existing callers retain the current project selection and
default-off rollout choice. Configured projects still require their existing
authorization. Rollout and flag retirement remain tracked in #340.
Opt in with exclude-vendored: true to ignore root vendor/ and third_party/
when no custom ignore is supplied. Nested and similarly named paths remain
eligible. A nonempty custom expression overrides this filter unchanged. The
compatibility default stays off until consumer rollout and retirement in #394.
.github/workflows/scan-for-todo-comments-readonly.yaml
is the internal catalogue smoke entrypoint. Two CI calls use dry-run: true to
verify one offline wrapper invocation for default and configured ignore inputs.
A third call exercises the actual production steps with optional integration,
an empty project and every source path excluded. All three grant only
contents: read and omit secrets; none can write issues or change a project.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | No | Preserved production interface; catalogue smoke callers must omit it |
dry-run |
Input (boolean) | false |
No | Catalogue callers explicitly enable offline execution |
ignore |
Input (string) | "" |
No | Repository-relative path expression forwarded to the wrapper |
exclude-vendored |
Input (boolean) | false |
No | Preserved production choice; the vendor smoke enables it |
optional-project-auth |
Input (boolean) | false |
No | Preserved production choice; the no-project evaluation enables it |
project |
Input (string) | organization/devantler-tech/5 |
No | Preserved project selection; the no-project evaluation sets it empty |
Generate it with bash .github/scripts/generate-todo-readonly.sh. The complete production
workflow is preserved, with only its display name changed and its issue permission removed.
Required CI checks compare it independently with production and reject restored caller/callee
write permissions, secrets, input drift or weakened execution. Production consumers continue
using scan-for-todo-comments.yaml with their existing authorization. Live project behavior
and optional project-authentication rollout remain tracked separately in #340.
.github/workflows/scan-for-todo-comments-default-fixture.yaml is a generated catalogue CI fixture. It retains the production branch and input defaults while replacing external token generation, checkout and Docker execution with offline dependencies. GitHub evaluates the original composite conditions. Required positive and broken-token callers verify default-off routing, the default project and exact token forwarding without creating issues or changing projects. This is wrapper coverage, not live token generation or project-adoption proof.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | No | Retained source interface; never pass a key to this fixture |
dry-run |
Input | false |
No | Retained production default; the fixture executes the production branch |
ignore |
Input | "" |
No | Forwarded scanner exclusion pattern |
exclude-vendored |
Input | false |
No | Retained compatibility default, omitted by the positive caller |
optional-project-auth |
Input | false |
No | Retained compatibility default, omitted by the positive caller |
project |
Input | organization/devantler-tech/5 |
No | Retained project default, checked by the offline dependency |
fixture-skip-app-token |
Input | false |
No | Fixture-only deliberate fault proving missing token output is rejected |
Click to expand
.github/workflows/scan-for-workflow-vulnerabilities.yaml is a workflow used to perform static analysis on GitHub Actions workflows using Zizmor.
jobs:
zizmor:
uses: devantler-tech/.github/.github/workflows/scan-for-workflow-vulnerabilities.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
actions: read
security-events: writeClick to expand
.github/workflows/sync-cluster-policies.yaml is a workflow used to sync upstream Kyverno policies to a target directory.
Which policies are synced is controlled by a .policyignore file at the repo root. It uses gitignore-style syntax β ordered glob patterns, one per line, where a leading ! re-includes a previously excluded path β so you can exclude everything by default and whitelist just the policies you want:
# Ignore every categoryβ¦
other*
# β¦except these two policies.
!other/create-pod-antiaffinity/create-pod-antiaffinity.yaml
!other/spread-pods-across-topology/spread-pods-across-topology.yamlPatterns are evaluated per file with last-match-wins, so a ! re-include still applies even when a broad earlier pattern matched its parent directory.
The file must exist and be readable. A missing or unreadable file stops the sync before any policy changes; an explicitly empty file includes every upstream policy. Matching follows Git's path rules: basename patterns apply at every depth, a leading / anchors at the upstream root, * stays within a path component, ** can span directories, and a trailing / matches directories only.
A literal ! re-include (one without glob characters) must still exist upstream. If upstream moves or drops that policy, the run fails and names the path before the target directory changes. Without that check it would open a pull request that deletes your vendored copy.
The selected policies are copied and checked beside the target directory before it changes. If the copy or the swap fails, the run fails and the target keeps its previous policies. A run that selects nothing empties the target only when .policyignore excludes every upstream policy; otherwise it fails. Entries in the target whose names start with a dot are left in place.
Set dry-run: true and omit APP_PRIVATE_KEY to validate the interface without syncing or opening a pull request. A real sync requires the App key and fails before token creation if it is missing.
jobs:
sync-cluster-policies:
uses: devantler-tech/.github/.github/workflows/sync-cluster-policies.yaml@<full-commit-sha> # vX.Y.Z
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
with:
kyverno-policies-dir: policies/kyverno| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | For a real sync | GitHub App private key; omit for dry-runs |
kyverno-policies-dir |
Input (string) | - | Yes | Directory to sync Kyverno policies to |
dry-run |
Input (boolean) | false |
No | Skip sync and PR creation (validate workflow interface only) |
Click to expand
.github/workflows/template-sync.yaml keeps a repository in sync with an upstream template repository via AndreasAugustin/actions-template-sync, opening a PR with any incoming template changes. List the files this repository owns (and that must never be overwritten by the template) in a .templatesyncignore file at the repo root β everything else the template ships is kept in sync.
With use-app-token: true, the sync protects pins in both devantler-tech/.github and the retired devantler-tech/actions catalogue. Within each repository, it compares commit ancestry: wherever the template pins a shared workflow or action to an older or diverged commit, the sync keeps this repository's line and says so in a warning. It stops without signing instead when it cannot keep that line safely: the template also changed the rest of the line, a file here pins the same component at more than one commit and the template moved one of those lines to the older pin, or an older or unverified replacement moved to another file. Equal pins and upgrades sync as usual.
Migration from devantler-tech/actions to devantler-tech/.github is allowed without comparing the two repositories' separate histories. Reusable workflows keep their .github/workflows/ path; public actions move beneath actions/ in the new catalogue. Once a consumer uses the new location for a component, a template that moves it back to the retired catalogue is refused before signing, including when files are renamed or calls are consolidated across files. Unchanged use of both catalogues and changes that only remove pins remain allowed. Migrating other calls does not permit downgrading a pin already in the new catalogue, including a pin removed from a different file. These cross-file checks examine the final proposed tree after any safe line restorations. If several calls move between files, each replacement must be at least as new as every removed pin for that component; ambiguous changes stop for manual review.
With use-app-token: true, a sync that would change no files does not stay open. This happens when the template is behind this repository on a pin: the sync proposes the older pin, the step above keeps this repository's line, and nothing is left to merge. The workflow then closes that pull request with a note instead of signing an empty commit. It keeps the generated branch, moved back onto the base commit, so the same template commit is not proposed again on the next scheduled run; a new template commit gets a new branch and syncs normally. Do not delete that branch while the template still lags, or the next scheduled run proposes the same commit again. It only ever touches the branch the same run generated, at the commit that run pushed.
on:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
template-sync:
uses: devantler-tech/.github/.github/workflows/template-sync.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: write
pull-requests: write
with:
source-repo-path: devantler-tech/platform-tenant-templateBy default the sync PR is opened with GITHUB_TOKEN, so GitHub does not trigger
on: pull_request or on: push workflows for the resulting branch or PR. This
prevents content copied from a compromised or malicious template from reaching a
caller's CI trust boundary before review.
Set use-app-token: true and pass APP_PRIVATE_KEY only when CI must run before
the sync PR is reviewed. This opt-in mints a write-scoped App token and permits
workflow-file updates. The workflow replaces the action's Git-created commit
with an equivalent GitHub API commit made by the App and verifies its signature
before moving the generated branch. The App-authored update also causes the
template-controlled PR content to trigger caller CI. Callers that opt in must
treat the PR as untrusted: do not expose secrets or write-scoped tokens to jobs
that check out or execute its content, including local actions and scripts.
An opt-in caller must wire both the input and the corresponding secret:
jobs:
template-sync:
uses: devantler-tech/.github/.github/workflows/template-sync.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: write
pull-requests: write
with:
source-repo-path: devantler-tech/platform-tenant-template
use-app-token: true
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}When the caller has its own .templatesyncignore, the sync applies that committed
copy and never the template's, so an entry the template adds after the caller was
created does not apply to it by default. Set merge-template-ignore-entries: true to fix that: before the
sync, the workflow appends every template entry the caller's list lacks, under a
marked comment, and the sync PR carries the merged list. The caller's own entries
are kept. To keep a single template entry out, add a !<entry> line to the caller's
list. With use-app-token: true, the merge and the sync are signed as one commit.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | When use-app-token |
GitHub App private key (paired with the APP_CLIENT_ID variable) |
source-repo-path |
Input (string) | - | Yes | owner/repo of the upstream template to sync from |
upstream-branch |
Input (string) | main |
No | Branch of the template repository to sync from |
pr-title |
Input (string) | chore: sync changes from the upstream template |
No | Title of the sync PR (Conventional-Commit by default) |
pr-commit-msg |
Input (string) | chore: sync changes from the upstream template |
No | Commit message for the sync PR |
pr-labels |
Input (string) | dependencies,automation |
No | Comma-separated labels for the sync PR |
pr-branch-name-prefix |
Input (string) | chore/template-sync |
No | Prefix for the branch the sync PR is opened from |
template-sync-ignore-file-path |
Input (string) | .templatesyncignore |
No | Path to the file listing consumer-owned (non-synced) files |
merge-template-ignore-entries |
Input (boolean) | false |
No | Add the template's ignore entries this repository's list lacks before syncing |
use-app-token |
Input (boolean) | false |
No | Opt in to a signed App-authored sync PR that triggers the caller's CI |
dry-run |
Input (boolean) | false |
No | Skip the sync and PR creation (validate workflow interface only) |
Note: The calling workflow runs the sync job with
contents: writeandpull-requests: write(declared by the reusable workflow).
The catalogue refreshes its declared GitHub CLI release digests weekly and on demand through the reviewed digest updater. It resolves the shared installer version, validates every supported platform, then opens a signed draft containing only the digest manifest. No-change runs mint no write token. The draft must pass normal review and CI before its digests become installer authority; unsupported custom versions retain their warning and existing verification behavior.
Click to expand
.github/workflows/update-agent-skills.yaml is a workflow used to keep installed agent skills (Copilot, Claude Code, β¦) up-to-date via gh skill update --all, opening a PR with any changes. Each installed SKILL.md's metadata.github-* frontmatter is the source of truth β no lockfile is required. Works with any mix of gh skill-compatible upstreams.
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
jobs:
update-agent-skills:
uses: devantler-tech/.github/.github/workflows/update-agent-skills.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: write
pull-requests: write
with:
dir: .agents/skills
# Optional: keep vendored skills out of `npx skills add <owner>/<repo>` listings.
mark-internal: trueThe workflow assumes skills were previously installed with devantler-tech/.github/actions/setup-agent-skills (or gh skill install directly) β the committed SKILL.md files carry the upstream pointers.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
dir |
Input (string) | . |
No | Directory to scan for installed skills (passed to gh skill update --dir) |
unpin |
Input (boolean) | false |
No | When true, pass --unpin (clear pinned versions) |
mark-internal |
Input (boolean) | false |
No | When true, set metadata.internal: true on every SKILL.md under dir after updating, so npx skills stops offering vendored skills as the repository's own |
gh-version |
Input (string) | 2.90.0 |
No | Minimum required gh version (must support gh skill) |
pr-per-skill |
Input (boolean) | false |
No | When true, open one PR per changed skill (from <pr-branch>-<skill>, titled <pr-title> (<skill path>)) so a blocked skill holds back only itself; a change outside every skill fails the run. Opt in only once any follow-up job, auto-merge rule or review exemption matching the exact pr-branch also accepts the per-skill branches. The skills output lists each skill and its branch suffix |
pr-branch |
Input (string) | deps/agent-skills-update |
No | Branch the update PR is opened from |
pr-title |
Input (string) | chore(deps): update agent skills |
No | Title of the update PR |
pr-labels |
Input (string) | dependencies,automation |
No | Comma-separated labels for the update PR |
commit-message |
Input (string) | chore(deps): update agent skills |
No | Commit message for the update PR |
dry-run |
Input (boolean) | false |
No | Skip update and PR creation (validate workflow interface only) |
use-app-token |
Input (boolean) | false |
No | Create the update PR with a GitHub App token so it triggers the caller's CI |
APP_PRIVATE_KEY |
Secret | - | When use-app-token is true |
GitHub App private key, paired with the APP_CLIENT_ID variable |
Note: The calling workflow must grant
contents: writeandpull-requests: writepermissions.
Click to expand
.github/workflows/validate-go-project.yaml is a workflow used to lint and test Go projects across multiple operating systems.
- Automated Linting: Runs
golangci-lintandmega-linterto ensure code quality - Auto-fix: Runs fixers in every validation and, when explicitly enabled, commits their fixes through the signed-commit workflow. Read-only runs fail with the remaining diff instead of discarding it
- Copilot Integration: When linting fails, automatically prompts Copilot on the PR to fix the remaining issues
- Supply-chain Scanning: Runs
govulncheckusing a reviewed scanner version and an allowlist evaluator shipped with the workflow to fail the PR on known vulnerabilities your code actually calls (call-graph reachability, so imported-but-unreachable advisories don't block). A consumer can risk-accept a reachable advisory that has no upstream fix (Fixed in: N/A) by committing an optional.govulncheck-allow.txtat the repo root (oneGO-YYYY-NNNN # justificationper line;#comments and blank lines ignored); the gate stays strict for everything else. Without an allowlist file the scan is strict. The scanner uses the Go version from the selected moduleβsgo.mod; Go 1.25, 1.26, and 1.27 are covered by the compatibility fixtures. Scanner failures and invalid output always fail the check. A scan attempt that runs out of time is retried once, so a slow runner cannot leave an unattended dependency pull request stuck on a cancelled check; a finding or any other failure fails the check immediately and is never retried. - Code Coverage: Generates a Cobertura report and uploads it to GitHub Code Quality (native PR coverage).
jobs:
go-test:
uses: devantler-tech/.github/.github/workflows/validate-go-project-readonly.yaml@<full-commit-sha> # vX.Y.Z
permissions:
contents: read
pull-requests: read
code-quality: write # required for GitHub Code Quality coverage upload
with:
pr-owner: ${{ github.event.pull_request.user.login }} # optional
apply-signed-fixes: falseNote: The calling workflow must grant
code-quality: writeso coverage can be uploaded to GitHub Code Quality. Coverage requires the repo's Code Quality to be enabled (Settings β Code quality).
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
APP_PRIVATE_KEY |
Secret | - | No | GitHub App private key for authenticating the workflow |
pr-owner |
Input (string) | - | No | Pull request author login. Signed fixes exclude exactly dependabot[bot], dependabot, renovate[bot], renovatebot and renovate |
apply-signed-fixes |
Input (boolean) | true |
No | Commit each fixer lane's auto-fixes back to the pull request branch as a signed commit (on by default; the org-required direct run is opted in by its workflow ref). Pass false to keep a caller read-only. Forks, Dependabot/Renovate branches and non-PR events are always read-only and fail with their diff if changes remain; other same-repository automation branches (release, bot-authored) do receive fixer commits like any contributor branch |
working-directory |
Input (string) | "" |
No | Go module directory to validate. Empty means the repository root |
go-memory-limit |
Input (string) | 8GiB |
No | Soft Go heap limit for deadcode and vulnerability analysis; lower it for smaller runners. Decimal Go units are accepted up to 8GiB; total runner memory is not capped. |
manual-workflow-fixes |
Input (boolean) | false |
No | Opt in to complete workflow-file patches for manual application, including after lint errors; existing upload eligibility still applies |
test-default-branch |
Input (boolean) | true |
No | Run the Go test suite on every default-branch run, not just when the diff touched a Go file. On by default: a test can take a non-Go file as its subject, so a diff-only gate leaves the default branch reporting green over a suite it never ran. Set to false to accept a default branch that can report green without the suite having run |
maintenance-default-branch |
Input (boolean) | false |
No | Also run tidy and dead-code analysis on default-branch pushes that change Go files. Findings fail validation without committing fixes to the default branch. Uses the repository's configured default branch name. |
.github/workflows/validate-go-project-readonly.yaml
runs the same lint, fix-export, build, test and coverage steps as Go validation,
without credentials that can mutate repository content, issues or pull requests.
Fixes fail with their diff; no signer is reachable. PR and status reporters are
disabled. Coverage uploads retain their dedicated code-quality: write scope.
Callers grant only contents: read, pull-requests: read and
code-quality: write, and forward no secrets. Catalogue CI uses this entrypoint
for all six Go fixtures. The ordinary Go workflow retains reporting and signed fixes.
This workflow is generated from the production workflow by
bash .github/scripts/generate-go-readonly.sh. Change the production source or
generator and regenerate it; CI checks the complete projection and credential boundary.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
pr-owner |
Input (string) | - | No | Pull request author login |
working-directory |
Input (string) | "" |
No | Go module directory; empty selects the repository root |
go-memory-limit |
Input (string) | 8GiB |
No | Soft Go heap limit for analysis; lower it for smaller runners, up to the 8GiB ceiling |
apply-signed-fixes |
Input (boolean) | false |
No | Ignored: signed fixes are always disabled |
manual-workflow-fixes |
Input (boolean) | false |
No | Prepare workflow-file fixes even after lint errors; patch upload is disabled and lint errors still fail |
test-default-branch |
Input (boolean) | true |
No | Run the suite on default-branch invocations |
maintenance-default-branch |
Input (boolean) | false |
No | Run tidy and dead-code analysis for Go changes on the default branch |
To enable default-branch maintenance validation, pass maintenance-default-branch: true
in a caller that runs on pushes to its default branch. Pull-request checks remain
enabled without this input. Tidy also preserves checks on other branches; its
default-branch exclusion uses the repository's configured name rather than
assuming main or master. Go path filtering and merge-queue exclusions still apply.
Rollout and flag retirement are tracked in devantler-tech/actions#1170.
The manual Repository admin-team audit workflow checks every active repository
visible to an all-repository App installation, including repositories outside
deploy/. It reads effective team permissions and requires exactly one admin
team with the admins slug. Archived repositories are excluded. The Admins team
must be secret, matching its declaration and excluding inherited child-team access
beneath it. API failures,
partial pagination, unknown permissions and inventory changes produce an unknown
result rather than a policy pass. Logs contain aggregate counts only.
Known built-in roles use GitHub's documented permission field when effective
Boolean metadata is absent. Typed permissions.admin metadata supports custom
roles when returned by the API. Unknown custom rights, malformed metadata and
conflicting built-in rights remain UNKNOWN. Both forms use the same canonical
permission join for policy evaluation and repeated-read stability checks.
The input-free manual workflow runs only from reviewed main. Its App token requests repository
Metadata and Administration read permissions; missing grants fail token creation.
A short-lived App JWT separately reads the authenticated installation's identity,
all-repository selection and suspension state, binds it to that token's installation
ID, and rechecks it after the audit. The JWT goes only to a fixed GitHub GET endpoint;
the key and request configuration use private temporary files and are removed.
GitHub's installation read
provides this proof; its repository-list response
provides the independent pagination totals. Installation mode requires this reviewed
main workflow context and its unrestricted token mint.
A skipped run is not evidence of compliance. The routine combined workflow also runs
daily and reports only complete outcomes. Legacy maintenance retirement remains #84.
An operator with organization-admin visibility can evaluate the shared checker
with bash scripts/check-repository-admin-teams.sh --organization-admin.
That explicit mode binds active admin membership to the organization identity
and checks the complete census against independently returned public and private
repository counts. It never substitutes for a failed App read or proves the App's
selection or grants. Every mode repeats the complete team join and inventory read;
changed repository or team identity, visibility or admin permission reports UNKNOWN.
See actions/CONTRIBUTING.md for conventions and guidelines.