Some repositories need more than the three core workflows. Each addition below has a fixed name and purpose, so that the same file means the same thing everywhere. All of them follow the rules in The three workflows: full-SHA references, permissions: {} at the top, and no repository code in a job that can write.

WorkflowTriggerPurposeToken access
docs.ymlPush to the default branch; pull requestBuild the site and manual; deploy to GitHub Pages from the default branch.Build contents: read; deploy pages: write, id-token: write
fuzz.ymlSchedule; manual dispatchRun fuzz targets for a bounded time and keep reproducers.contents: read
bench.ymlSchedule; manual dispatchMeasure performance against a recorded baseline.contents: read
codeql.yml, scorecard.ymlPush, pull request, scheduleStatic analysis and repository-practice scoring.As each tool documents
link-inputs.ymlPull request touching producer inputs; manual dispatchBuild, test and attest native linker inputs.contents: read, attestations: write, id-token: write
publish-link-inputs.ymlManual dispatch from the default branchPublish an attested candidate and commit its lock.See Trusted publisher contract

Risks and controls for sustained campaigns§

A separate campaign job can still run on every PR, and a cancelled search can look like a successful time-bounded run. Separate workload intent and evidence. Use explicit schedule/manual event guards, normal bounded search completion, per-input limits and an overall safety timeout. Preserve reproducers and exact compiler/source identities on failure. Check the actual triggers and reproduce a saved failing input. Campaign results describe the selected search; neither their success nor a short PR smoke run substitutes for published-release compatibility or a meaningful benchmark measurement.

Documentation site§

docs.yml has two jobs.

The build job checks out the repository, builds the site, and uploads it as a Pages artifact. A repository with an AsciiDoc manual uses actions/build-docs, which also produces the PDF. A pull request runs this job, so a broken manual fails review, and the artifact can be downloaded to preview the change.

The deploy job runs only for the default branch. It has pages: write and id-token: write, uses the github-pages environment, checks out nothing, and deploys the artifact the build job produced.

What the site points at§

A visitor to the site is almost always a user, and a user has the latest release. The site therefore leads with the latest release, not with the default branch:

"Latest" excludes prereleases; see Release candidates.

The site may also carry the default branch’s documentation, under a path and a label that mark it as unreleased, and the documentation of earlier releases, each restored from its own archive. Nothing rendered is committed: the site is assembled at build time from the working tree and from release assets.

A new release changes what "latest" means, so release.yml rebuilds and deploys the site after it publishes, in a job that holds only the Pages permissions and deploys an artifact built by a read-only job.

Fuzzing§

Fuzz targets are written with roc-fuzz; see Correctness and memory properties with roc-fuzz for why and how. Named regression examples, memory assertions and accepted allocation baselines run in fast PR checks. This workflow adds sustained exploration; roc-fuzz’s default 60-second run is not a PR smoke command.

fuzz.yml runs sustained campaigns on a schedule or manual dispatch. It has no pull-request trigger, and campaign jobs explicitly accept only those two event types on the default branch:

if: >-
  (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') &&
  github.ref == format('refs/heads/{0}', github.event.repository.default_branch)

The same guard is required if a campaign job shares a workflow file with PR jobs. A separate job or runner alone does not keep expensive work out of PR CI. Campaign jobs have contents: read, disable persisted checkout credentials and run no publishing or pin-update controller.

Resolve the latest Roc nightly once and record its exact tag and roc version, the tested source commit, platform release/digest, target, arguments and duration in the report. Define whether a budget applies per target or to the complete campaign; ten minutes per target across dozens of targets is not a ten-minute job. Bound each input’s time and memory as well as the campaign’s search time, and leave headroom in the job timeout for building, cleanup and uploading logs.

Use TARGET run CORPUS --report-dir=PATH to check declared examples and exact baselines before exploration, and write one supervised report. Choose a fresh report directory for each invocation. Baseline failures skip exploration; verification never invokes accept. Missing baseline entries are unbaselined, so review and commit the intended baseline inventory before relying on it.

Restore and save the evolving corpus through the Actions cache, separately from the small committed PR smoke corpus. Cache absence starts a new search; it never excuses a missing target. Preserve crash inputs, raw stdout/stderr and the run metadata as artifacts even when testing fails. Reproduce findings with the saved input, not the random seed alone. A property failure, crash or per-input timeout, allocation assertion or baseline change fails the job. A campaign that reaches its configured search limit normally may pass. Zero accepted exploration inputs are inconclusive and unsuccessful. Job cancellation, missing reports and infrastructure timeouts are incomplete work, never a successful campaign.

ci.yml gives every target a smoke run: it builds the target, checks named examples and accepted baselines, and replays the small committed corpus within the one-second workload budget. That keeps targets building between campaigns. Sustained searches and their --opt=speed builds stay in the campaign job.

Daily checks of Roc builtins§

roc-fuzz’s builtin targets exercise compiler-provided operations such as list sorting, strings and dictionaries. Run a bounded daily campaign against the latest Roc nightly, for example a ten-minute total search budget distributed across the selected targets. New nightlies can change these operations without any change to roc-fuzz, so a daily campaign can find upstream regressions that PR smoke runs and published examples do not expose.

Keep this as fuzz.yml, with its own status and failure artifacts. It tests the selected builtin targets and search configuration, while shared nightly compatibility tests the immutable published product examples. Neither result substitutes for the other. Investigate a failure, minimise and preserve its reproducer, and report an upstream issue when the evidence points to Roc. Compiler-pin changes remain a separate maintainer decision.

Benchmarks§

bench.yml measures on a schedule and on request, never on a pull request. ci.yml gives every benchmark a smoke run, one small iteration built with --opt=dev and at most one second of workload execution, so that a benchmark cannot fall behind the API it measures. Smoke timings are not performance measurements and must not update baselines or fail a performance threshold.

Use the same schedule/manual event and default-branch guard as fuzz.yml. Measured runs use --opt=speed, documented warmups, samples and runner conditions. Record source and compiler identities beside the baseline and raw measurements so a comparison is interpretable. Hosted-runner variance needs an explicit measurement policy; do not turn timing noise into a PR failure.

For a ceiling-based allocation ratchet, fail when the count exceeds the bound and lower the ceiling when it improves. roc-fuzz’s accepted allocation baselines use exact matching instead, so both increases and reductions need review. See Allocation properties and exact baselines for choosing and maintaining these checks.

Linker inputs§

Platforms that consume prebuilt native inputs have a producer workflow and a publisher workflow. These are the only route by which native build inputs are released. Maintain a platform explains when a platform needs them, and Publish linker inputs from a pull request specifies both.