> Documentation version: main. [HTML page](https://lukewilliamboswell.github.io/roc-blueprint/contributing.html)

<a id="contribute"></a>

# Contribute

Use issues for bugs and design discussion, and pull requests for changes.
Report a suspected vulnerability privately, as
[SECURITY.md](https://github.com/lukewilliamboswell/roc-blueprint/blob/main/SECURITY.md) describes.

<a id="find-your-way-around"></a>

## Find your way around

```
blueprint-platform/   the roc-blueprint platform that Blueprint.roc apps use
  *.roc                  setting types, checked values, lowering to the Spec
  host/, build.zig       Zig host, built into targets/<target>/libhost.a
  targets/               linker inputs, none committed: libhost.a is built, the rest fetched (see its README)
blueprint-core/          roc-blueprint-core: Spec, Provider contract, validation, Steps, Value, codec
  Project.roc            pure normalization, references and provider capability checks
  Realise.roc            pure planning of a request: what the Core supplies, and what it asks of a provider
  Sbom.roc               pure SBOM inventory; writes SPDX 3.0.1, SPDX 2.3 and CycloneDX 1.7, reads SPDX 3.0.1, 2.3 and 2.2 and its own CycloneDX
  Spdx3.roc, Spdx2.roc   the SPDX 3.0.1 and 2.x rules, held to the schemas pinned in tests/sbom/schema/
  CycloneDx.roc          the CycloneDX 1.7 rules, held to the schema pinned in tests/sbom/schema/
  fuzz/                  roc-fuzz targets: spec-parse, spec-round-trip, project-validate,
                         lock-parse, lock-snapshot-parse, lock-snapshot-round-trip,
                         lock-snapshot-project, sbom-render, sbom-read, build-record-parse,
                         build-model-compare, nix-native-decode, nix-lock-decode
blueprint-nix/           importable pure Nix provider (depends only on blueprint-core)
  NixProvider.roc        shared pure request planning and flake rendering
  Locks.roc              Nix pins <-> the Lock: Sources plus a "nix" hint
  tests/                 Spec fixtures and golden flakes
blueprint-cli/           the blueprint CLI (basic-cli + weaver), which executes provider plans
  Store.roc, Download.roc  the per-user store of downloads and the HTTPS client that fills it
  StoreRecord.roc        the record the store keeps beside a tree, read and written without effects
  Bootstrap.roc          identity of the compiler archive embedded in each CLI
  compilers.roc          Bootstrap and StoreRecord as a package for build tooling and tests
  Published.roc          what a release publishes as the digest of an asset, asked by update
fixtures/consumer/       independent consumer of the Spec and Nix packages
fixtures/overlays/       overlay flakes for scripts/test_scenarios.roc
examples/all-settings/   environments, sources, scoped overlays, tasks and Raw
examples/composition/    imported pure modules: reusable task settings and a program's recipe
examples/artifacts/      runnable source/dependency/workflow example and its Roc scripts
examples/extensions/     Custom blocks; CI checks blueprint refuses them clearly
scripts/                 Roc scripts, run from the repository root; their modules are in scripts/src/
  test.roc               local CI test groups; runs the test_*.roc suites
  link_inputs.roc        fetch or check the platform's linker inputs
  bundle.roc             bundle core, the platform and the reporting app into dist/ and smoke-test them
  embed_compiler.roc     stage and verify one target's compiler archive for embedding
  test_bootstrap.roc     test embedded compiler installation, repair and concurrency
  build_release.roc      cross-build the CLI, the SBOM tool and the smoke-test program for each released system
  smoke_binary.roc       run a built CLI with no Roc and no Python of its own; with --no-nix, with no Nix either
  fuzz.roc               replay or fuzz the roc-fuzz targets
  release_notes.roc      render a release's notes from docs/releases/
  package_examples.roc   archive the examples with a release's platform URL
  package_sbom_fixtures.roc  archive the SBOM fixture set with its case contract
  test_release.roc       test that archive against the bundles, served from localhost
  docs_snippet.roc       write the part of the manual that names the latest release
  check_version.roc      check a release's version before building and against the public API after
  package_docs.roc       archive the rendered site
  release_assets.roc     gather a release's assets and check the names other repositories depend on
  test_lock_snapshot.roc  check the official lock snapshot consumer against the vendored schema and profile
  test_builds.roc, test_workflows.roc   real-Nix build and workflow suites on scripts/src/BuildHarness.roc
  reproducible_probe.roc  compare a producer on independent CI hosts and synthetic CLI patch builds
docs/                    the manual: index.adoc includes one chapter per top-level .adoc file
  releases/              each release's notes, included by releases.adoc
link-inputs.lock.json    the linker-input release the platform links, pinned by content
flake.nix                builds blueprint with the pinned Roc; the development shell; the release cross-compiler
```

The local platform and CLI share ` blueprint-core `, including
` Project.validate `. The Spec wire format is major 3; older platform bundles are not compatible. The architecture, terminology (Spec,
Provider, Lock, stages) and invariants are defined in
[the architecture chapter](architecture.md#purpose); keep it in sync with changes.

<a id="review-provider-neutrality"></a>

## Keep capabilities provider neutral

[Provider neutrality](architecture.md#provider-neutrality) is a lasting
design constraint, including for a future executor developed within
Blueprint. Review shared-contract changes against it:

- Describe the capability and guarantee independently of the current Nix
  implementation. Keep native syntax, command lines and metadata in the
  provider; keep identity semantics explicit and provider-qualified.

- Show how another deterministic system could fulfil the contract. A
  provider that cannot fulfil it must fail before effects, rather than
  silently weaken isolation or reproducibility.

- Preserve the boundary between committed intent and pins, pure inspection,
  captured execution evidence, disposable materialization and the host’s
  constrained effect execution.

- Exercise changed provider boundaries with an independent contract fixture
  using its own arguments, identities and metadata. Renaming a Nix fixture
  is not evidence of another implementation.

- Document unsupported capabilities and remaining routing or registration
  constraints. Distinguish a reusable contract from an implemented backend.

Protocol changes need a shared semantic reason and the existing versioning
rules. They must not make every provider adopt one ecosystem’s native format.
Production support for a second provider and general resolver composition
remain follow-up work tracked in
[the additional-provider follow-up](https://github.com/lukewilliamboswell/roc-blueprint/issues/56).

Review changes against the ownership table in the architecture chapter. Scripts
own build algorithms; the pure core owns validation and plans; the host owns
effects; providers own native execution and caches. Identity fields must say
whether they identify input content, a prepared build or measured output.
Do not reconstruct a build graph from independent JSON reports when a shared
plan/result type can serve the interface.

<a id="set-up"></a>

## Set up

Full development and CI require x86\_64 Linux with Nix and flakes enabled.

``` sh
nix develop
```

gives two Roc compilers from
[roc-overlay](https://github.com/roc-lang/roc-overlay), Zig 0.16 and the
programs the scripts start: coreutils, git, curl, tar, gzip, zip, unzip and gh.
On Linux it also supplies Bubblewrap, util-linux and Python for isolated
runner tests and a restrictive-umask launcher. Nothing else uses Python: the
product, the reporting app and every conformance check run in Roc.
` roc ` is the product compiler, the nightly in
` blueprint-cli/compiler-version `: it builds and tests the CLI, the packages and
the platform, and a released ` blueprint ` embeds it. ` roc-stable ` is the
nightly ` rocStableTag ` names in ` flake.nix `: the scripts in ` scripts/ ` start
with ` #!/usr/bin/env roc-stable ` and run on it, so they keep working while the
product moves to a newer nightly. The scripts reach the product compiler
through ` ROC `, or ` roc ` on ` PATH `. This shell is the one definition of the toolchain:
every CI job runs its scripts through ` nix develop -c `, so what passes in the
shell is what CI runs. ` blueprint ` is not in the shell, so entering it never
compiles the CLI; build it as below, or use ` nix run . `.

The shell stages this host’s pinned compiler archive as the ignored file
` blueprint-cli/compiler-archive `. Roc imports its bytes into the CLI at build
time. Outside the shell, stage an archive first with
` scripts/embed_compiler.roc <system> `; the script checks its size and digest
against ` Bootstrap.roc `. Keep the complete archive: macOS Roc needs the
` darwin/ ` sysroot beside its executable.

` blueprint-cli/main.roc ` uses the released basic-cli platform by URL. Roc
downloads it on first use outside Nix; the Nix blueprint package fetches the same
archive by hash, so its sandboxed build needs no network.

<a id="build-and-test"></a>

## Build and test

``` sh
scripts/link_inputs.roc fetch             # the musl runtime files the platform links
(cd blueprint-platform && zig build)      # libhost.a or host.lib for all five hosts
roc test blueprint-core/main.roc      # Spec round trips and format tests
roc test --opt=interpreter blueprint-cli/main.roc # CLI, provider and core expects
roc build blueprint-cli/main.roc --output=./blueprint
(cd examples/all-settings && ../../blueprint run --help) # try the CLI
scripts/test.roc                            # all local CI test groups
scripts/test.roc unit cli                   # or only some groups: static, unit,
                                            # cli, scenarios, builds, package, glue, fuzz
scripts/fuzz.roc 300                        # fuzz each target for 5 minutes
```

The CLI unit tests use interpreter mode because the pinned compiler’s
native test backend crashes on the large embedded byte constant. Integration
tests run the compiled executable.

The ` .roc ` files in ` scripts/ ` are Roc programs; they start other programs by
argument list, not through a shell. Run one from the repository root with the
pinned ` roc ` on ` PATH `, or as ` "$ROC" scripts/test.roc `.

The platform’s linker inputs are not committed. ` scripts/link_inputs.roc fetch `
downloads the release pinned in ` link-inputs.lock.json ` into ` .cache/link-inputs/ `,
verifies it and installs the files beside ` libhost.a `; nothing that evaluates a
` Blueprint.roc ` against the local platform links without them.
` scripts/link_inputs.roc check ` verifies what is installed without the network.
See
[blueprint-platform/targets/README.md](https://github.com/lukewilliamboswell/roc-blueprint/blob/main/blueprint-platform/targets/README.md).

` scripts/test.roc ` runs the repository’s CI test groups. Its groups are ` static `
(formatting; no tracked object file, archive or import library; the CLI’s use of
the Provider contract; the compiler downloads ` Bootstrap.roc ` records, one
for each system the CLI runs on; each workflow’s
permissions and checkouts; the release notes chapter against the notes files;
the flake’s list of core modules and its version), ` unit ` (the platform’s, the scripts', the CLI’s
and the consumer’s ` expect `s, the configuration fixtures and the Roc the
manual shows), ` cli `,
` scenarios ` (` test_scenarios.roc `, ` test_roc_packages.roc ` and the golden
flakes parsed by Nix), ` builds ` (` test_builds.roc ` and ` test_workflows.roc `),
` package ` (the flake’s package and development shell, the packaged CLI building
an artifact with only Nix on ` PATH `, and a dry run of what a release ships:
core and the platform bundled and smoke-tested, the examples archived and the
archive tested; see [The two bundles](maintaining.md#the-two-bundles)),
` glue ` (` scripts/check_glue.roc `: the host’s committed
` roc_platform_abi.zig ` is what ` roc glue ` writes for the platform with the
product compiler; ` scripts/check_glue.roc --update ` rewrites it)
and ` fuzz ` (each target’s committed corpus, replayed, which
takes seconds; ` .github/workflows/fuzz.yml ` fuzzes each target for five minutes
every week, starting from the inputs earlier runs found, which it keeps in the
Actions cache through ` FUZZ_CORPUS `, and uploads any crashing input). ` scenarios ` and ` builds ` are the
real-Nix suites, split into two groups of about the same length so that CI runs
them as two jobs at the same time.
Two additional CI jobs run ` scripts/reproducible_probe.roc ` on independent
Ubuntu 24.04 and 22.04 hosts. One uses the release binary; the other compiles
a synthetic next-patch CLI with the same runner pins. Each builds and
rebuilds the same selected-input producer. A dependent job compares their
actual derivation, outputs and locked-source provenance.
` scripts/test_config.roc ` runs the compiler on generated ` Blueprint.roc `
fixtures: the platform’s own rules, which only a compiler run can show (a
setting given twice or not at all, in ` Lower.roc `, and each checked name’s
` from_quote `), a few core rules and bounds as witnesses that shared validation
surfaces at compile time, and that settings composed from an imported module
emit the same Spec as inline ones. Every other rule of ` Project.validate ` has an
` expect ` in ` blueprint-core/Project.roc `. The bundle gate repeats a few of the
fixtures against the served platform, plus the all-settings example.
It then compiles the manual’s Roc (` scripts/src/DocSnippets.roc `): every
` [source,roc] ` block of ` docs/ ` and ` docs/releases/ `, and the first program of
the Get started chapter, against the platform in the working tree; see
[Write the manual](#write-the-manual).
` scripts/test_cli.roc ` exercises the built CLI with and without a
configuration, checks validation and help/version handling, and records Nix
argv to verify shell selection and task arguments without entering a shell.
It holds what only a run can show: the order of checks before any effect, how
often the configuration is evaluated, exact provider argv and error text. Rules
that a pure ` expect ` in ` blueprint-core ` or ` blueprint-nix ` already holds are
not repeated there.
The all-settings integration tests separately run tasks through real Nix.
` scripts/test_scenarios.roc ` writes projects and runs them through the real
CLI and real Nix: the all-settings and extensions examples, noncommutative
overlays in both orders with inheritance, an unselected overlay’s native
missing-package failure and a declared overlay that throws if evaluated,
system-scoped tools on Linux and macOS with the package-target rejection of an
unscoped one, renamed commands, and a task that names a script by path, as
` scripts/check_all.roc `, which starts by its ` roc-stable ` shebang on a platform
` RocPackages ` supplies and imports a module beside it, and zig-overlay and
rust-overlay as overlays, each supplying an exact release named in a quoted
attribute, and configured tools: a fixture overlay’s package says which
options it was built with, and rust-overlay evaluates a toolchain with two
targets and a component. It also runs ` nix develop --debug ` on a
staged flake and requires Nix’s own report that its bash comes from the locked
package source, with no registry lookup,. A project it writes pins its inputs to the
revisions in ` fixtures/consumer/inputs.lock ` and ` flake.lock `, and its
` Blueprint.lock ` is read-only and compared after every step.
` fixtures/consumer/main.roc ` holds the staged bytes and supplied-lock
preservation as ` expect `s. Parse fuzzing also checks successful semantic
normalization for idempotence.
SBOMs are SPDX 3.0.1 JSON-LD, with SPDX 2.3 and CycloneDX 1.7 renderings of
the same inventory for consumers that read only those, and conformance is
checked in Roc, with no other validator. ` blueprint-core/Spdx3.roc `,
` Spdx2.roc ` and ` CycloneDx.roc ` hold the classes or objects, members,
enumerations and prose rules of each format, and ` scripts/src/SbomTest.roc `
holds them to the digest-pinned schemas in ` tests/sbom/schema/ ` (the published
SPDX 3.0.1 JSON schema, the SPDX 2.3 schema and the CycloneDX 1.7 schema):
every class, member and enumeration the validators accept is the schema’s own.
The same expects require that each committed fixture in ` tests/sbom/fixtures/ `
is what the renderers write now (regenerate them with
` scripts/test_sbom.roc fixtures tests/sbom/fixtures `), that all three
renderings of a fixture read back to one package graph, and that the
standalone tool’s committed inputs (` input/ `, ` source-import/ `), the case
contract (` CONTRACT.md `) and the file list of the release asset
(` scripts/src/SbomFixtures.roc `) are what the suite states (regenerate the
first three with ` scripts/test_sbom.roc inputs `, ` source-import ` and
` contract `). ` scripts/test_sbom.roc binding `
then runs the built ` scripts/sbom.roc ` against read-only inputs: the native
case, and the source supplier import’s accepted targets, bound renderings
and refusals as ` source-import/expected.json ` states them. The
` sbom-render ` fuzz target generates inventories with arbitrary text and
graphs: each must render to valid, canonical SPDX 3.0.1, SPDX 2.3 and
CycloneDX 1.7, read back from each to exactly its own packages and
relationships, with CycloneDX’s standard fields (dependencies, scopes, hashes,
purls, licenses, suppliers) derived from the inventory, and the validators
must reject a rendering once one of a list of SPDX 3.0.1 or CycloneDX rule
violations is injected into it. It also strips Blueprint’s properties from
the CycloneDX rendering, which must then import as plain CycloneDX to a
projection of the inventory, and adds files to the SPDX renderings, which
must read to the same package graph with the files counted as gaps.
` sbom-read ` mutates the fixtures of every format, including what Syft and
Trivy write (` tests/sbom/fixtures/upstream/ `): reading must never crash, what
it accepts must be a sound package graph, and it must import as an upstream
SBOM unless the import is refused for a developer path, a credential, its
size or a subject digest that is not a SHA-256.
` scripts/test_builds.roc ` runs real sandboxed builds of artifacts,
dependencies and sources, including host-file and TCP isolation with positive
host controls, fail-closed runner checks, exact argv, immutable locks,
freshness and relocation. It also checks what Nix copies from the project
(bytes, the owner’s execute bit, raw names, exclusions, refused symlinks and
special files) and that an undeclared tool is not found. The exact standalone
runner’s argv, input and output checks run in an isolated Linux test harness;
an unisolated invocation must refuse execution before user code runs. Runtime
namespace witnesses are private to each invocation and do not enter the
derivation. Stubbed CLI tests verify their contents, cleanup and separation
from stable recipe bytes.
` scripts/test_workflows.roc ` runs real ordered task/build workflows, nested
repetitions, failure stops, snapshot/dependency freshness, repeated
locked-source verification, whole-closure preflight and immutable authority,
including out-of-tree layouts, and then a temporary copy of the complete
artifacts example. Both are Roc scripts on the harness in
` scripts/src/BuildHarness.roc `, and the tasks and builds of their fixtures are
Roc scripts run as ` roc-stable ` through ` RocPackages `. Their inputs are pinned
in ` fixtures/consumer/inputs.lock ` and ` fixtures/roc-inputs.lock.json `; one
first step fetches them and every later Nix call is recorded and run with
` --offline `. See [fixtures/builds/README.md](https://github.com/lukewilliamboswell/roc-blueprint/blob/main/fixtures/builds/README.md).
` scripts/test_update.roc ` checks local-source preflight, authority observation,
concurrent publication, named updates and what an unsupported lock is told. ` scripts/test_isolation.roc ` checks what a build stages
about its caller: the namespace identities, the CLI’s own path as the runner,
and the refusals when either cannot be used. Both put a failing
` python3 ` on ` PATH `: the CLI itself must not use a host Python.
` scripts/test_programs.roc ` runs projects whose environments fetch programs
and expect tools of the machine. Nothing selects how they run but their own
` Blueprint.roc `: one with no Nix tool must never start the ` nix ` stub. The
downloads come from a server the script runs on the loopback address, the
store is a directory of the test’s own, and the lock entries are written by
the test. It checks what is kept in the store and under which names, that a
second run makes no request, that a stored artifact with other bytes is
removed with its tree and fetched again, two runs at once from an empty store,
every refusal (a missing expected program, a program the lock does not hold or
holds under other URLs, another length or digest, each URL’s failure, an
archive with a link out of its tree), the caller’s own shell, and parity: the
same task with one Nix tool added prints the same from the same store file,
and only then starts Nix. Its last cases let ` blueprint update ` write the
lock: with no ` nix ` on ` PATH ` for a project that only fetches, with nothing
asked when nothing changed, for a named program alone, after a URL list
changed, and for release assets. A download URL must be ` https:// `, with one
exception made for these tests: ` http://127.0.0.1:<port>/ `, which no other
machine can serve. A loopback URL of the shape
` /<owner>/<repo>/releases/download/<tag>/<asset> ` is a release asset, and the
same server describes its release at ` /repos/<owner>/<repo>/releases/tags/<tag> `,
as GitHub’s API does for a GitHub URL. That is the whole seam: no variable
or flag redirects the question. Through it a digest computed here is compared
with a published one, another system’s is recorded unfetched, a published
digest that differs stops the update, a question that is refused or answered
with something else leaves every digest computed and says so once, and a
download missing for one system says how to declare that.
It ends with ` blueprint requirements `, asked with no ` nix `, no lock, an empty
store and no server: the report is the same, a declared and unused Nix source
requires no provider, and nothing is written.
These are Roc scripts, run from the repository root with ` ./blueprint `
built; ` ROC ` names the compiler. They replace every tool the CLI runs with one
program, ` scripts/stubs/tool.roc `, which each test builds once and installs
under several names (` roc-record `, ` roc-wire `, ` roc-probe `, ` nix `, ` guix `,
` python3 `, ` readlink `). A copy decides what to be from its own file name and
how to behave from ` STUB_* ` variables, and records each invocation as a file
holding one line of JSON in ` <name>-calls/ ` under its working directory. Its
header lists what each name does. ` scripts/src/CliHarness.roc ` holds what the
three tests share.
` scripts/test_roc_packages.roc ` is a Roc script, like ` link_inputs.roc `. It
runs the real CLI and Nix with a Roc package cache of its own in a temporary
directory, and refuses to run if that directory lies inside ` ~/.cache `. A
task’s bundles are downloaded by ` blueprint ` itself from the releases their
URLs name. It checks publication into an empty cache, that a second run
writes nothing, that an existing package is left alone and an incomplete one
replaced, that failures leave nothing behind, that a project with no build
locks no bundle and its task still downloads them, and that a sandboxed build
resolves its bundles from the lock with no network while the same build
without one of them does not.
Normal execution tests explicitly initialize authority with ` update ` first.
The complete artifacts example is executed in a temporary copy by
the workflow suite (` scripts/test_workflows.roc `).

<a id="keep-fuzz-coverage-with-the-code"></a>

### Keep fuzz coverage with the code

Every reader of bytes that Blueprint did not write in the same process has a
fuzz target in ` blueprint-core/fuzz/ `: committed authority (the Spec:
` spec-parse `, ` spec-round-trip ` and ` project-validate `; the Lock:
` lock-parse `), reports it reads back (the lock snapshot: ` lock-snapshot-parse `,
` lock-snapshot-round-trip ` and ` lock-snapshot-project `; SBOMs: ` sbom-read ` and
` sbom-render `), native tool output a provider decodes (Nix derivation, build
and dev-shell JSON: ` nix-native-decode `; the native lock graph and the Nix
pins in the Lock: ` nix-lock-decode `) and local records (build records:
` build-record-parse ` and ` build-model-compare `).

- A reader has a mutation target with a seeded corpus and a dictionary. A
  value with a generator also has a generation target for its round-trip and
  idempotence properties.

- A pull request that adds or changes such a reader adds or extends its
  target in the same change. The ` static ` group keeps the target matrix of
  ` .github/workflows/fuzz.yml ` equal to the directories of
  ` blueprint-core/fuzz/ `, so CI cannot forget a new target. Reviewers ask of
  every new parser what reads it, and which target covers it.

- A corpus holds one valid document per schema version, one seed per refusal
  the parser names, and every crashing input ever found, committed under
  ` corpus/ ` with a name that says what it found. The replay on every commit
  then guards it for good.

- CI replays the corpora on every commit, which takes seconds. The weekly
  workflow fuzzes each target for 300 seconds in parallel jobs and keeps what
  it finds in the Actions cache, so each week starts where the last ended.
  ` scripts/fuzz.roc SECONDS [TARGET...] ` runs a longer campaign on request.
  The weekly budget is set in ` fuzz.yml `, and changing it is a deliberate
  decision recorded there.

- Only pure code is fuzzable: a reader that lives in the CLI app moves to a
  pure module in ` blueprint-core ` or an adapter before it can have a target.

<a id="the-job-with-no-nix"></a>

## The job with no Nix

Every suite above runs where Nix is installed, and replaces it with a stub
where it must not be started. One job of ` .github/workflows/ci.yml ` runs on
machines that have no Nix at all: ` no-nix `, on ` ubuntu-latest `,
` ubuntu-24.04-arm `, ` macos-latest `, ` macos-15-intel ` and ` windows-latest `, with the binaries and platform hosts
the ` build ` job produced and no step that installs anything. It runs
` dist/smoke-<system> --no-nix dist/blueprint-<system> `, the ` NoNix ` mode of
` scripts/src/SmokeBinary.roc `, which:

- refuses to start when ` nix ` is on ` PATH `, and leaves no ` roc ` there;


- writes a project whose compiler is
  ` Programs([Fetch("roc-stable", Recipes.roc_nightly(...))]) ` of the nightly
  in ` blueprint-cli/compiler-version `, with ` Expect(["git"]) ` and the Roc
  packages of a basic-cli script, against this checkout’s platform;

- gives the binary a store and a Roc package cache of its own, empty;


- runs ` blueprint update ` and requires that the embedded compiler was
  installed exactly once, for reading ` Blueprint.roc ` and as ` roc-stable ` together;

- runs the shared compiler as ` roc-stable version ` and requires its actual
  response to match ` blueprint compiler-version `;

- requires that ` blueprint requirements --json ` names no provider;


- runs the script as a task, which the embedded compiler builds, links and
  runs. On macOS that is the test that ` roc ` finds the ` darwin ` directory of
  its archive through the link it is started by;

- runs the task again and requires that nothing is downloaded, and that no
  flake was generated.

The same job then runs the cross-built SBOM tool of its system,
` dist/blueprint-sbom-<system> `, over ` tests/sbom/fixtures/input `
(` .github/actions/sbom-tool `): ` inspect ` must reproduce the committed
inventory, ` bind ` writes the sample artifact’s sidecars and ` check ` holds
them, all with no Roc on the machine.

It reaches GitHub for the nightly and the Roc packages, and asks GitHub’s API
once for the nightly’s published digests; a refusal there is a notice, not a
failure. To run it on a machine that has Nix, hide Nix from ` PATH `:

``` sh
roc build scripts/smoke_binary.roc --output=/tmp/smoke
env PATH=/usr/bin:/bin /tmp/smoke --no-nix ./blueprint
```

<a id="write-the-manual"></a>

## Write the manual

The manual is the AsciiDoc in ` docs/ `. ` index.adoc ` is a book that includes
one chapter per top-level ` .adoc ` file; ` .github/workflows/docs.yml ` renders
it as a site and a PDF with the shared
[` build-docs `](https://lukewilliamboswell.github.io/roc-automation/actions.html#build-docs)
action, on every pull request, and deploys the site from ` main `. The action is pinned to a reviewed roc-automation main revision; the renderer
is released as ` tools-0.6.0 `. ` flake.nix ` checks the released
renderer binary’s SHA-256 for each supported host. Rendering uses the native
Roc utility. API generation uses this repository’s pinned Roc compiler.

To render the same outputs locally, inside ` nix develop `:

``` sh
scripts/docs_snippet.roc    # docs/generated/, from the latest release; needs a logged-in gh
renderer=$(nix build .#roc-docs --no-link --print-out-paths)/bin/roc-docs
"$renderer" build --workspace "$PWD" --pdf-filename roc-blueprint.pdf \
  --pdf-image-dpi 240 --site-url https://lukewilliamboswell.github.io/roc-blueprint/ \
  --docs-version main --api-entrypoint blueprint-platform/main.roc \
  --roc-command "$(command -v roc)"
```

The site is ` .docs-out/site/ ` and the PDF ` .docs-out/roc-blueprint.pdf `;
neither is committed. Both workflows use 240 DPI for PDF image preparation
so the architecture diagram retains detail when zoomed. Publication links use explicit URLs; this action and
renderer pair uses ` site-url ` for publication links and Markdown discovery.

Keep PSV table cells on complete physical lines. HTML honours the declared
column proportions; this renderer release’s PDF sizes earlier columns to
their content and gives the last column the remaining space. Keep long
explanations together: the overview combines each source and its version pin,
and the configuration checks use labelled paragraphs. Stage responsibilities
use separate input/output, execution and invariant tables. Review both
outputs after changing a table; column proportions alone do not establish
that its PDF is readable.

Stable explicit anchors preserve links when a heading’s wording changes.
Invariant headings use words before their numbers because numeric-leading
headings exceed this release’s native inline profile. The historical
` use-this-release-2 ` release anchor remains explicit. Prepared figures replace
render-time Mermaid and JPEG conversion, with their source and asset digests
recorded below. These are authoring changes, rather than renderer patches.

Native images are non-interlaced RGB/RGBA PNGs. The manual’s illustration is
converted from the original JPEG; its source hash and conversion command are
in ` docs/images/blueprint-illustration.provenance.json `. The architecture flow
uses the checked-in published SVG and its prepared PNG, with provenance in
` docs/images/blueprint-flow.provenance.json `. Its Mermaid source stays in
` architecture.adoc `; ` docs/prepared-diagrams.json ` binds its exact source and
PNG hashes. Update the figure and registration together when editing the
Mermaid block. These prepared assets let ordinary documentation builds run
without a browser, image converter or diagram tool.

The manual describes the tool as it is: one recommended way to do each thing,
in the present tense. What changed between versions belongs in the release
notes, and what is unfinished in an issue.

The Roc in the manual is compiled by ` scripts/test.roc unit `, so a block must
be one of five things:

- a whole ` Blueprint.roc `, starting ` app [ `, whose platform is written as a
  placeholder;

- settings as they stand inside ` config `, complete enough to check by
  themselves: an environment a task uses is in the block. A tool of the
  source ` nix ` may be written without the ` Packages("nix", ...) ` line;

- ` [source,roc,snippet=core-module] `, pure imports and declarations checked
  inside a package against the repository’s core modules;

- ` [source,roc,snippet=refused] `, settings the compiler must refuse, for a
  mistake the text explains;

- ` [source,roc,snippet=unchecked] `, which is not compiled: use it only for a
  line of some other program or for settings that cannot stand alone.

- Each chapter opens with one ` = Title ` and is also rendered as a page of its
  own, so it cannot rely on attributes set in ` index.adoc `.

- Link to another chapter as ` xref:chapter.adoc#section-id[text] `, always with
  a section’s ID and text. Section IDs come from headings, lowercase with
  hyphens.

- Give tables explicit column widths.


- A release’s notes are ` docs/releases/<version>.adoc `, included from
  ` releases.adoc `, newest first; see
  [Release](maintaining.md#release).

