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.
| Workflow | Trigger | Purpose | Token access |
|---|---|---|---|
docs.yml | Push to the default branch; pull request | Build the site and manual; deploy to GitHub Pages from the default branch. | Build contents: read; deploy pages: write, id-token: write |
fuzz.yml | Schedule; manual dispatch | Run fuzz targets for a bounded time and keep reproducers. | contents: read |
bench.yml | Schedule; manual dispatch | Measure performance against a recorded baseline. | contents: read |
codeql.yml, scorecard.yml | Push, pull request, schedule | Static analysis and repository-practice scoring. | As each tool documents |
link-inputs.yml | Pull request touching producer inputs; manual dispatch | Build, test and attest native linker inputs. | contents: read, attestations: write, id-token: write |
publish-link-inputs.yml | Manual dispatch from the default branch | Publish 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:
its primary API reference is the
roc docsoutput generated for the latest release, restored from that release’s documentation archive;its examples link is the examples archive attached to the latest release; and
its install snippet names the latest release’s bundle URL.
"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.