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

<a id="work-without-a-provider"></a>

# Work without a provider

A package provider is one place a project’s tools can come from, and
` blueprint ` starts one only for what a project takes from it. Nix is the
provider that ships today; the core supplies the rest itself: programs
fetched by digest, Roc packages, tools the machine is expected to have, and
the tasks, shells and activations that use them. A project that fetches its
programs, expects the rest of the machine and declares no build runs every
task with no provider installed. This chapter says what such a project looks
like, what each command then does, and where the downloads are kept.

<a id="a-project-that-never-starts-a-provider"></a>

## A project that never starts a provider

``` roc
app [config] { pf: platform "<the platform URL of a release>" }

import pf.Recipes

config = [
	Name("my-package"),
	Environment("dev", [
		Programs([Fetch("roc-stable", Recipes.roc_nightly("nightly-2026-10-28-bad769b"))]),
		Expect(["git"]),
		RocPackages([
			"https://github.com/roc-lang/basic-cli/releases/download/0.26.0-rc2/Gj3zVDr3qrQa6aTh7Z1zgUfe9NeBE6R76T5ho3xrc8af.tar.zst",
			"https://github.com/roc-lang/http/releases/download/1.0.0/6ZUwqYhCS8PU9Mo6MF7oV82ET2o7KYb57CLKDq4cq4sS.tar.zst",
		]),
	]),
	Shell("default", [Use("dev")]),
	Task("check", [Use("dev"), Run(["scripts/check.roc"])]),
]
```

Nothing in this file belongs to a provider: there is no ` Packages `, ` Input `,
` Source ` or ` Build `, and no tool written ` "<source>#<name>" `. The compiler
comes from a release by a digest in ` Blueprint.lock `, ` git ` is the machine’s,
and the Roc packages are downloaded by the URLs that name their hashes.

What starts a provider is decided by ` Blueprint.roc ` alone, never by a
variable, a flag or what happens to be installed:

| Request | Starts a provider when |
| --- | --- |
| ` blueprint run TASK `, ` blueprint shell `, ` blueprint env NAME ` | The environment has, on this system, a tool from a package source: ` Tools `, ` ToolsFor ` this system, ` Command ` or ` ToolWith `. An overlay the environment selects does not start a provider by itself. |
| ` blueprint workflow NAME ` | One of its tasks does, or it has a build. |
| ` blueprint build NAME `, ` plan build NAME --prepare ` | Always. A build runs through the executor it selects. |
| ` blueprint update ` | ` Blueprint.roc ` declares something a provider resolves: a package source, an ` Input `, a ` Source `, or a Roc package that a build’s environment has. An update that names only fetched programs never does. |
| ` blueprint check `, ` spec `, ` tasks `, ` requirements `, ` gen `, ` flake `, ` plan build NAME `, ` lock show `, ` store verify ` | Never. |

` blueprint requirements ` gives the first row’s answer for a project’s tasks
without running anything; see
[What tasks need](commands.md#what-tasks-need). Where a provider is
needed and its program, ` nix ` for the Nix provider, is not on ` PATH `, the
command stops before it downloads or runs anything and names the tool that
asked for it:

```
blueprint: this machine lacks what the task needs, and blueprint installs none of it:
  nix: environment host takes nix#zigpkgs."0.16.0" from the Nix source nix
```

<a id="what-each-command-does-without-a-provider"></a>

## What each command does without a provider

| Command | What happens |
| --- | --- |
| ` blueprint update ` | Records in ` Blueprint.lock ` the SHA-256 and length of every fetched program on every declared system. It downloads this machine’s programs and keeps them in the store; see [What update needs and records](commands.md#what-update-needs-and-records). |
| ` blueprint run TASK ` | Looks on ` PATH ` for every program the environment expects, and stops with all that are missing in one message. Then makes each fetched program ready in the store, puts the environment’s Roc packages where Roc looks for them, and runs the task’s command from the project root with the fetched programs first on ` PATH `. |
| ` blueprint workflow NAME ` | The same for each task in order. Everything every step needs is checked before the first step runs. |
| ` blueprint shell [NAME] ` | Does the same and then starts the caller’s own shell, the program ` SHELL ` names, or ` /bin/sh ` when it is unset, with the fetched programs first on ` PATH `. Leaving that shell ends the command. |
| ` blueprint ` / ` blueprint gen `, ` blueprint flake ` | Generate nothing: the project has no flake. ` blueprint flake ` prints nothing on standard output and says so on standard error. |
| ` blueprint check `, ` spec `, ` tasks `, ` requirements ` | Work as everywhere. They read ` Blueprint.roc ` and nothing else. |

A task still needs a ` Blueprint.lock ` that matches ` Blueprint.roc `: the
digests are in it. Evaluating ` Blueprint.roc ` needs the Roc compiler
` blueprint ` was built for, which it embeds and installs into the same store when
` ROC ` is unset and no matching ` roc ` is on ` PATH `.

<a id="the-store-of-downloads"></a>

## The store of downloads

Everything ` blueprint ` downloads for itself is kept in one per-user
directory, named by content:

```
<store>/v1/artifacts/sha256/<hex>                  the downloaded bytes
<store>/v1/trees/sha256/<hex>.<kind>/              those bytes, made usable
<store>/v1/trees/sha256/<hex>.<kind>.digest        what that tree held when made
```

` <store> ` is ` blueprint ` under ` XDG_CACHE_HOME `, or under ` .cache ` in ` HOME `
when that is unset; a relative value is refused. ` <kind> ` is ` file `,
` tar.gz `, ` tar.xz ` or ` zip `: a single file made executable, or an archive
unpacked whole, because a program may need what lies beside it. On macOS
` roc ` links with the ` darwin ` directory of its archive.

- **An artifact is never trusted**: It is hashed again before every use, wherever it came from: a download, an
  earlier run, a restored cache or a hand that put it there. One whose bytes
  are not those its name says is removed, with every tree made from it, and
  downloaded again. A download that does not have the length and the SHA-256
  in ` Blueprint.lock ` is not kept and nothing is unpacked from it.

- **A tree is never trusted either**: When a tree is made from an artifact that has just been verified, its digest
  is recorded beside it as S-expression text, never inside it: the content
  digest ` blueprint ` uses for local sources, over every file, directory and
  executable bit, with where each symbolic link leads. Before every use, after
  its artifact, the tree is hashed again and compared with that record, which
  only reads it. A tree that differs, or has no record, is removed and made
  again from the verified artifact, with no download. In CI, cache
  ` v1/artifacts ` alone, keyed on ` Blueprint.lock `: trees are rebuilt from it,
  and a cached artifact is re-hashed like any other. This check cannot see a
  tree and its record changed together; ` blueprint store verify ` compares both
  with a fresh extraction of the archive, and ` --repair ` makes them again. See
  [Verify the store](commands.md#verify-the-store).

- **An archive is listed before it is unpacked**: An archive that holds an absolute name, a ` .. ` segment, a name that is not
  plain text, a link that leaves the tree, or anything that is not a file, a
  directory or a link, is refused whole, and so is one that lacks the program
  the recipe names.

- **Two runs at once are safe**: Each file and tree is written under a fresh name beside its final one and
  renamed into place, so no half-written file is ever seen and two processes
  that fetch the same thing both succeed. There is one gap: a run that finds
  an artifact or a tree that is not what was recorded removes the tree, and
  another process that is running a program from it at that moment loses it.

The store may be deleted at any time; the next run fetches what it needs
again. The files of a tree are read-only and its directories are not, so an
ordinary ` rm -r ` removes it. A Roc nightly is about 67 MB as an artifact and
about 220 MB unpacked, and both are hashed on every use.

<a id="how-a-download-is-made"></a>

## How a download is made

` blueprint ` downloads over HTTPS itself. It sends no token, cookie or other
credential, and follows at most five redirects, each of which must be HTTPS.
A request that fails in transit or is answered 408, 429 or a 5xx status is
tried three times; a 404 is final at once. When a download has alternate
URLs (` Mirrored `), they are tried in order, and the message for a program no
URL served lists what each one answered. A whole download is held in memory
while it is hashed.

Roc packages are not kept in this store. They go into Roc’s own package
cache; see
[Lock the Roc packages scripts use](configuration.md#roc-packages).

<a id="what-is-on-path"></a>

## What is on PATH

For each step, ` blueprint ` makes a directory ` run/run-<random>/bin ` under the
generated directory (` .blueprint/ ` unless you moved it), holding one symbolic
link for each fetched program, named as ` Fetch ` names it and pointing at the
program inside its tree. On Windows, and wherever a symbolic link cannot be
made, the program is copied there with its mode instead. Nothing is exposed
through a hard link, which would be the store’s own file under a second name
that a step could write through. That directory is put first on the step’s
` PATH `, ahead of everything the caller had, and is removed when the step
ends, however it ends. A task whose command is the name of a fetched program
is started from that link whatever else is on ` PATH `.

Expected programs are not put anywhere: they are found on the caller’s
` PATH `, which the step inherits after the links. In an environment that also
has a provider’s tools, the provider’s shell is started with that ` PATH ` and
puts its own tools ahead of it; for Nix that is ` nix develop `.

<a id="programs-blueprint-still-starts"></a>

## Programs blueprint still starts

Three programs of the machine are used on this path, besides the compiler and
whatever the project expects:

| Program | Used for |
| --- | --- |
| ` tar ` | Listing and unpacking an archive. A ` Zip ` unpacks only where ` tar ` is bsdtar, as on macOS; GNU tar does not read a zip. |
| ` ln ` | The symbolic links of a step’s ` bin ` directory on Linux and macOS. |
| ` chmod ` | Making a downloaded ` File ` executable and a tree’s files read-only on Linux and macOS. |

<a id="what-still-needs-a-provider"></a>

## What still needs a provider

- A build, always: it runs through the executor it selects, and the Nix
  executor builds sandboxed on x86\_64 Linux only. That sandbox holds nothing
  of the machine, so a build’s environment may not have fetched programs or
  expected tools.

- Any tool from a package source, in the environments that have one. Other
  tasks of the same project run without the provider.

- ` blueprint update ` in a project that declares anything a provider resolves.
  Adding the first package source to a project therefore makes its lock stale
  for every task until someone with that provider runs ` blueprint update ` and
  commits the result; after that, the tasks that use no provider tool again
  run without it.

<a id="intel-macos"></a>

## Intel macOS

The release includes ` blueprint-x86_64-darwin `, built for the platform’s
` x64mac ` target. Current nixpkgs no longer builds for Intel macOS, so a
project’s tasks run there without a provider: from fetched programs, expected
tools and Roc packages. ` Recipes.roc_nightly ` selects the nightly’s
` macos_x86_64 ` archive. The ` no-nix ` CI job and the release’s tests run the
binary on ` macos-15-intel `: installing the embedded compiler, writing a lock,
reporting requirements, and building and running a basic-cli program.

` x86_64-darwin ` is in the default ` Systems `, so fetched programs are locked
for it unless a project lists its systems without it. Provider tools and
sandboxed builds remain unavailable.

<a id="windows"></a>

## Windows

The release includes ` blueprint-x86_64-windows.exe ` and the platform’s
` x64win ` target. The ` no-nix ` CI job checks installing the embedded compiler, writing a
lock, reporting requirements, and building and running a basic-cli program.

The store uses ` LOCALAPPDATA ` when ` XDG_CACHE_HOME ` is unset.
` Recipes.roc_nightly ` selects the nightly’s zip and ` roc.exe `; the Windows
` tar ` reads zip archives. Fetched programs are copied into a step’s ` bin `
directory with their mode, never hard-linked, and need no ` ln ` or ` chmod `.

Include ` "x86_64-windows" ` in the project’s ` Systems `: the default list
contains Linux and macOS only, and fetched programs are locked for the
declared systems.

Windows does not execute a script’s shebang. Name its interpreter in the task:
` Run(["roc-stable", "--no-cache", "hello.roc"]) `. The ` --no-cache ` flag
works around [Roc issue \#12429](https://github.com/roc-lang/roc/issues/12429):
the October 18 nightly reports duplicate symbols when linking cached Windows
objects, including on the first run with an empty cache. CI uses this
workaround on Windows and still builds, links and runs the program. It
disables Roc’s compilation cache for that invocation; Blueprint’s download
store and lock verification still apply.

Provider tools and sandboxed builds remain
unavailable. CI checks Windows Server; Windows 10 and 11 desktop installations
have not been verified here.

