Skip to content
Connect an agent
Get started

Enterprise rollout

How a platform team puts diagrams as code into hundreds of repositories: one reusable workflow, a required status check, owners for the overrides files, pinned and updated releases, self-hosted runners and GitHub Enterprise Server, an air-gapped setup, and the outputs other tools read. It is for the people who run CI for many teams.

For
  • GitHub Actions
  • GitHub

TL;DR

QuestionAnswer
How do teams adopt it?In GitHub Actions, call reusable workflows from a central repository, pinned by commit SHA. plotwork-examples (opens in a new tab) has working ones: plotwork-check.yml, plotwork-scan.yml and plotwork-regenerate.yml.
What permissions does it need?Check mode contents: read, and nothing else. Write mode contents: write and pull-requests: write. Code scanning adds security-events: write.
Is it safe on a fork's pull request?Check mode is: it reads no secret and makes no network request. Write mode and publishing run on a push or a schedule, never on a fork's pull request.
How is the check enforced?Make the job a required status check in a branch ruleset.
How do tools read the result?The changes and files outputs (JSON), exit codes, a SARIF file for code scanning, and a JUnit file for a test view.
One large repository for many teams?A settings file per team with working-directory, a matrix job, and since to check only what a pull request changed.
Can we verify a release?Yes. dist/checksums.txt in every release, and from the first release after v1.0.0-beta.6 a signed build provenance attestation: gh attestation verify dist/index.js --repo plotwork-official/plotwork-action.
Air-gapped?The Action and the command line run offline. Only publishing calls an API; the self-hosted engine draws with no outbound request.

1. Reusable workflows

The central repository holds the workflows; each repository calls them. Copies to start from are in plotwork-examples (opens in a new tab): plotwork-check.yml, plotwork-scan.yml and plotwork-regenerate.yml.

yaml
name: diagrams
on:  pull_request:  push:    branches: [main]  schedule:    - cron: '0 4 * * *'
permissions: {}
jobs:  check:    if: github.event_name == 'pull_request'    uses: acme/platform/.github/workflows/plotwork-check.yml@0123456789abcdef0123456789abcdef01234567    permissions:      contents: read    with:      changed-only: true
  regenerate:    if: github.event_name != 'pull_request'    uses: acme/platform/.github/workflows/plotwork-regenerate.yml@0123456789abcdef0123456789abcdef01234567    permissions:      contents: write      pull-requests: write    secrets:      PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }}

GitHub checks every job of a called workflow against the permissions its caller grants, even a job that would be skipped, and refuses to start the run when one asks for more. So each reusable workflow asks for one set of permissions:

WorkflowWhat it doesPermissions the caller grants
plotwork-check.ymlFails on drift; writes nothingcontents: read
plotwork-scan.ymlThe same, and uploads code scanning alertscontents: read, security-events: write
plotwork-regenerate.ymlMakes the diagrams again and opens a pull requestcontents: write, pull-requests: write

2. Required status checks

Add the check job's name (for the workflow above, check / Diagrams match their files) as a required status check in a branch ruleset on the default branch. A pull request whose diagrams drifted then cannot merge until its author runs plotwork generate or merges the pull request the nightly run opened.

Keep a nightly run without since: a new release of the Action can draw pictures differently, and no file change points at that.

3. Owners for the overrides files

Overrides are the only hand-written part of a generated diagram, so review them like code. A CODEOWNERS entry sends each change to the team that owns the diagram:

text
/teams/payments/docs/*.overrides.json   @acme/payments/plotwork.config.json                   @acme/platform

A generated .diagram file needs no owner: a hand edit to it is drift, and the check fails.

4. Fork pull requests and pull_request_target

EventWhat to runWhy
pull_requestmode: checkThe token is read-only on a fork and no secret is passed. Check mode needs neither, and makes no request.
pull_request_targetNothing from this guideIt runs with the base repository's secrets and a write token. Checking out the fork's head there runs the fork's files with them. Generating diagrams never needs it.
push, schedulemode: writeThe branch is the repository's own, so pushing and opening a pull request is safe.

on-drift: pr refuses to run on a pull request, and on-drift: commit needs the head branch checked out; neither works on a fork's pull request, by design.

5. Many teams in one repository

ShapeHow
A settings file per teamworking-directory: teams/payments finds teams/payments/plotwork.config.json. Paths in it start at that folder.
A job per teamA matrix over the teams' folders, each job with its own working-directory. The reusable workflow's concurrency group includes the folder, so teams never wait on each other.
Only what changedsince: ${{ github.event.pull_request.base.sha }} with fetch-depth: 0. A source runs when one of its files, its overrides file, its diagram, or the settings file changed; a deleted file runs every source.
paths filtersWorkflow paths: on the files the sources read and the settings file, so a pull request that changes neither starts no job.
Many sourcesUp to 250 sources in a settings file of at most 256 KB. Past that, split by team.
yaml
jobs:  diagrams:    strategy:      fail-fast: false      matrix:        team: [payments, search, identity]    uses: acme/platform/.github/workflows/plotwork-check.yml@0123456789abcdef0123456789abcdef01234567    permissions:      contents: read    with:      working-directory: teams/${{ matrix.team }}

6. Outputs other tools read

OutputContract
Exit code0 when every diagram matches its files, 1 on drift or an error (with fail-on: never, always 0), and the command line's 2 for a mistake in the command or the settings.
filesA JSON array of paths from the repository root: the drifted diagrams in check mode, the files written in write mode.
changesA JSON array, one object for each drifted diagram, in the order of the settings: name (the source's name), out (its file from the repository root), changes (a count of changes by meaning), summary (one sentence). Fields are only ever added, never renamed or removed, within a major version.
publishedA JSON array, one object for each published source: name, kind (personal or organization), url, stableLink (or null), svgUrl (an organisation's picture, served to a key; or null), id, organization.
sarif-fileSARIF 2.1.0: rule plotwork/drift for each drifted diagram on its first line, and one rule for each diagnostic code on its line. Upload it with github/codeql-action/upload-sarif and the drift shows in the Security tab.
junit-fileJUnit XML: one test case for each source, failed on drift or an error.
Command lineplotwork generate --check --format json, --format sarif, or --format junit prints the same to standard output.

7. Pinning and updates

Pin the Action by its full commit SHA, with the release in a comment, and let Dependabot move it:

yaml
- uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14

The samples pin the latest release of the Action by its commit. @v1 becomes available at the first stable release.

yaml
# .github/dependabot.ymlversion: 2updates:  - package-ecosystem: github-actions    directory: /    schedule:      interval: weekly

Each release's dist/checksums.txt holds the SHA-256 of the bundle and its notices; sha256sum --check dist/checksums.txt in a checkout of the release verifies them.

Every release after v1.0.0-beta.6 also carries a signed build provenance attestation for dist/index.js and dist/checksums.txt. The Action's public repository signs it with GitHub artifact attestations when the release is published, after checking the checksums, and records it in the public Sigstore transparency log. In a checkout of the release, with the GitHub CLI:

bash
gh attestation verify dist/index.js --repo plotwork-official/plotwork-action

To require the release workflow and the tag as well, add --signer-workflow plotwork-official/plotwork-action/.github/workflows/release.yml --source-ref refs/tags/v<version>. The attestation proves the file is the one in the release tag and was signed by that repository's release workflow. The bundle is built from a private repository, so it does not describe that build.

A release can draw some pictures differently, and then the Dependabot pull request that moves to it makes those pictures stale; merge it together with the regenerated diagrams, which the nightly write run opens as a pull request.

8. Runners, GHES, and air-gapped networks

SetupWhat to know
Self-hosted runnersThe Action runs on the runner's node24, with git on the path for write mode. It needs no other tool, and no install.
GitHub Enterprise ServerThe Action reads GITHUB_SERVER_URL and GITHUB_API_URL, so pushing and pull requests go to the server. Copy the release into the server, or sync it with GitHub Connect.
No internetCheck and write mode make no request to Plotwork: the importers and the renderer are in the bundle. Leave publish off. The command line (npx @buun_group/plotwork generate) runs offline the same way, from an internal npm mirror.
Hosted pictures inside the networkThe self-hosted engine, one container, serves the drawing tools and the /v1 drawing routes with no outbound request. It has no accounts or hosting, so publishing stays off.

9. Keys and privacy

QuestionAnswer
Which key?An organisation key publishes into the organisation's library and needs no other setting. A personal key with an alias publishes a stable link.
Where does it live?A repository or organisation secret, passed as PLOTWORK_API_KEY to the step that publishes, in write mode only.
Can it leak?It is masked with ::add-mask:: before any request, never printed, and never sent to GitHub.
What leaves the runner?With publishing on, the generated diagram source and the repository's defaults and brand, to the API. The files the diagrams are made from never do.
Which API?PLOTWORK_API_URL, or the hosted one. A key works only on the service that made it, and the refusal names the address it called.

10. Failures to plan for

SituationWhat happens
A file that does not parseAn annotation on its line, and that source writes nothing. The others still run.
A source whose files are missingS103, on its line of the settings file.
An overrides file a source names that is not thereWarning S203; the diagram is made without overrides.
A protected branch refuses the pushThe error names the rule and says to use on-drift: pr, or let the token bypass it.
A person pushed to the pull request branchThe action leaves the branch as it is and says so; it never replaces someone else's commit.
GitHub rate limits, or a server errorEach request is tried up to four times, waiting as the answer asks.
Two runs at onceGive each workflow a concurrency group by branch and folder, and never cancel a write.
A summary too largeThe summary keeps under GitHub's 1 MiB limit, shortening the change tables first; the outputs and the log keep everything.

See also

PageWhy
Diagrams as codeSources, overrides, and the Action's inputs
Keep diagrams in sync in CIThe other commands of the Action
Self-hosting the engineThe engine inside your network
OrganisationsOrganisation keys and the library