# Reference

---

# Recipe format reference

_pekit / Reference_

> The complete schema for pekit's pekit.toml and package.pekit.toml: every section, key, type, default, and the exact strictness rules the loader enforces.

This is the normative schema for the two TOML files at the heart of a pekit
recipe: `pekit.toml` (the recipe proper) and `package.pekit.toml` (what to
package). It enumerates every section, every key, its type, whether it is
required, and its meaning. Behaviour that a key merely *triggers* is
cross-linked to the concept pages; this page is the field-by-field contract.

Two strictness rules hold throughout and are called out per-section below:

- **Unknown keys are hard errors.** Every table the loader owns has a fixed
  set of accepted keys. Any other key — including a camelCase spelling of a
  known snake_case key, such as `outDir` — is rejected with an
  `unknown_key` diagnostic. There is no lenient/forward-compatible mode.
- **Types are checked on decode.** A key declared here as a string must be a
  string, a table must be a table, and so on; a mismatch is an `invalid_type`
  error.

## `pekit.toml`

### Worked example

```toml
out_dir = "dist"

[env]
CFLAGS = "-O2"
PREFIX = "/usr"

[wrap]
command = ["pesb", "run", "--", "{{command}}"]

[source.git]
url = "https://github.com/example/tool.git"
ref = "v{{version}}"
versions = ">=1.0"
tag_regex = "^v(?P<version>[0-9.]+)$"

[build]
command = "make PREFIX=$PREFIX"

[build.docs]
command = ["make", "docs"]
needs = ["main"]

[build.linked]
command = "make link"
dependencies.pesb-dev.libc = ">=2.38"

[test]
command = "make check"

[install]
command = "make install DESTDIR=$PEKIT_OUT"

[clean]
command = "make clean"
```

### Top-level keys

The recipe file accepts exactly these top-level keys. Anything else is an
`unknown recipe key` error.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `out_dir` | string | no | Output directory name, relative to the recipe root. Default `"out"`. |
| `env` | table | no | Environment variables exported to every target command. See [`[env]`](#env). |
| `wrap` | table | no | Command wrapper applied to every target. See [`[wrap]`](#wrap). |
| `source` | table | no | Where the recipe's source tree comes from. See [`[source]`](#source). |
| `delegate` | bool or table | no | Borrow build/env/wrap/package definitions from the source tree. See [`[delegate]`](#delegate). |
| `source_package` | table | no | Corresponding-source package emission control. See [`[source_package]`](#source_package). |
| `build` | table | no | Build target(s). See [`[build]` / `[test]` / `[install]` / `[clean]`](#targets). |
| `test` | table | no | Test target(s). |
| `install` | table | no | Install target(s). |
| `clean` | table | no | Clean target(s). |
| `gen` | table | no | Source-generating target(s). See [`[gen]`](#gen). |

### `[env]`

A flat table of environment-variable name → value. Every entry is exported
into each target command's environment (layered on top of the `PEKIT_*`
contract; see [Anatomy of a recipe](/pekit/recipes/anatomy.md)).

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| *&lt;NAME&gt;* | string | — | Value for variable *NAME*. |

Each *NAME* must match `^[A-Za-z_][A-Za-z0-9_]*$`; anything else is an
`invalid_env` error. Values must be strings. Declaration order in the file is
preserved, so a later entry may reference an earlier one via shell expansion
in the target command.

### `[wrap]`

Wraps every target command in another command (for example, running it inside
a sandbox launcher).

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `command` | string or array of strings | no | The wrapper. Must contain the placeholder `{{command}}` exactly once. |

No other key is permitted under `[wrap]`. The `{{command}}` rules mirror a
target `command`:

- **String form:** the whole string is run through a shell; it must contain
  the substring `{{command}}` exactly once.
- **Array form:** `{{command}}` must appear as a *complete* argument (not a
  substring of a larger argument) exactly once, and never as the first element
  (element `0` is the wrapper program itself).

An omitted `command` yields an empty wrap (no wrapping).

### `[source]`

Selects and configures the source tree. It has three sub-tables. At most one
**reproducible** source (`git` or `url`) may be present — declaring both is a
`mixed_source` error. A `local` source may be combined with a reproducible one
(the local path acts as an override). Unknown keys directly under `[source]`
are rejected.

| Sub-table | Meaning |
| --- | --- |
| `[source.git]` | Clone from a git repository. |
| `[source.url]` | Download from a URL (optionally an archive). |
| `[source.local]` | Use a directory on disk. |

One direct key is accepted alongside the sub-tables:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `patches` | string | no | Name of a recipe-root directory (a single path segment) whose `series` file pekit applies to the materialised source tree before any target runs. Requires a reproducible source (`patches_source` otherwise). See [Patches](/pekit/recipes/sources.md#patches). |

See [Sources](/pekit/recipes/sources.md) for materialisation, caching, and
provenance behaviour. Resolved git and url sources are pinned
trust-on-first-use in the recipe's machine-written
[`pekit.lock`](/pekit/reference/supporting-files.md#pekitlock).

#### `[source.git]`

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `url` | string | **yes** | Git remote URL to clone. |
| `ref` | string | no | Ref (tag/branch/commit) to check out. Templated. Default `"{{version}}"`. |
| `versions` | string | no | Version **cap**: a constraint string filtering enumerated or requested versions (see [Versions](/pekit/recipes/versions.md)). |
| `tag_regex` | string | no | Regex extracting version numbers from tag names during enumeration. |

#### `[source.url]`

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `url` | string | **yes** | URL to download. Templated with the version. |
| `extract` | bool | no | Treat the download as an archive and extract it. Default `false`. |
| `root` | string | no | Sub-directory within the extracted tree to use as the source root. Default `"."`. |
| `versions` | string | no | Version **cap**: a constraint string filtering enumerated or requested versions (see [Versions](/pekit/recipes/versions.md)). |
| `file_regex` | string | no | Regex extracting version numbers when enumerating from a listing. |
| `checksum` | string **or** table | no | Expected checksum. A bare string applies to all versions; a table maps version → checksum. |
| `signature` | table | no | Upstream signature verification — see `[source.url.signature]` below. |

#### `[source.url.signature]`

Optional sub-table of `[source.url]`. Its presence makes upstream signature
verification **required**: the detached signature is fetched and verified
against the pinned keys before a version is locked or built. A missing
signature is `signature_missing`; a failed verification is
`signature_invalid`; either aborts the run and writes no lock entry.
Verification is in-process OpenPGP — no host `gpg` is involved.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `key_files` | string array | **yes** | Committed public key files (armoured or binary OpenPGP), resolved relative to the recipe root. Must be non-empty. |
| `url` | string | no | Signature URL template. `{{source_url}}` expands to the rendered artifact URL; version variables are also available. Default `"{{source_url}}.sig"`. |
| `of` | string | no | What the signature covers: `"artifact"` (the published file, default) or `"decompressed"` (its decompressed content — kernel.org's `.tar.sign` signs the uncompressed tar). Any other value is `invalid_signature`. |
| `fingerprints` | string array | no | Allowlist of signer fingerprints (hex; spaces and `0x` ignored, case-insensitive). When set, a valid signature by any other pinned key is `signature_untrusted_key`. |

#### `[source.local]`

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `path` | string | **yes** | Path to the source directory, resolved relative to the recipe root. |

### `[delegate]`

Lets a recipe borrow definitions that live *inside* the fetched source tree
rather than in the recipe directory. May be written as a bare boolean
(`delegate = true`, equivalent to `all = true`) or as a table.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `all` | bool | no | Delegate everything (build, env, wrap, packages). |
| `build` | bool | no | Delegate build targets. |
| `env` | bool | no | Delegate `[env]`. |
| `wrap` | bool | no | Delegate `[wrap]`. |
| `packages` | bool | no | Delegate package definitions. |

Any other key is an `unknown delegate key` error. A category is delegated when
either its own flag or `all` is set.

### `[source_package]`

Controls the **corresponding-source package** a recipe emits alongside its
binary packages. Emission is automatic — the table exists only to opt out or
rename. A recipe emits one when both of these hold: it has a reproducible
`[source]` (`git` or `url`; local overrides and bare branch refs never
qualify), and it produces at least one `peipkg`-format package.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `name` | string | no | Package name. Default `<recipe-dir>-source`. |
| `enabled` | bool | no | Set `false` to emit no source package. Default `true`. |

Any other key is an `unknown source_package key` error.

The emitted package is `noarch`, versioned identically to the recipe's package
members (members that disagree on version are a
`source_package_version_conflict` error), licensed as the conjunction of the
members' licenses, and installs under `/usr/src/dist/<name>-<version>/`
(with any `-source` suffix stripped from `<name>`):

- `upstream/` — the pristine source input: a url source's downloaded artifact
  byte-for-byte, so its hash matches the committed `pekit.lock`, or a
  `git archive` export of the locked commit.
- `patches/` — the recipe's patch series, when a `patches/` directory exists.
- `recipe/` — the build-controlling files from the recipe directory:
  `pekit.toml`, package definitions (including `packages.pekit/`), env files,
  `pekit.lock`, and `keys/`. `*.keyring.pekit.toml` files are never included.

Every `peipkg`-format member the recipe emits carries the source package's
name in its manifest's `build.source_package` field, linking each binary to
its corresponding source.

### `[build]` / `[test]` / `[install]` / `[clean]` {#targets}

These four sections define **targets** — the commands pekit runs. Each section
takes one of two shapes, distinguished by whether a `command` key is present
directly in the section:

- **Bare target:** `command` (and the other target keys) sit directly under
  the section, e.g. `[build]` with `command = "make"`. This defines a single
  target named `main`.
- **Named targets:** the section contains sub-tables, e.g. `[build.docs]` and
  `[build.linked]`, each a target. Each name must be a valid selector.

Mixing the two — a bare `command` alongside a named sub-table in the same
section — is a `mixed_targets` error. (A sub-table whose key is itself a target
key, such as `[build.dependencies]`, is read as part of the bare target, not as
a named target.)

Fields of a single target:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `command` | string or array of strings | **yes** | The command. String form runs through a shell; array form is an argv executed directly. An empty array is rejected. |
| `needs` | array of strings | no | Names of other targets in the same section that must run first. |
| `clear_out` | bool | no | Wipe this target's output directory before running. Default `true`. |
| `dependencies` | table | no | **Build targets only.** Build-time dependencies the target needs provisioned. See below. |
| `sign` | table | no | **Build targets only.** Files in the target's output to sign after the command succeeds, by signature kind. See [`sign`](#target-sign) below. |

Any other key in a target table is an `unknown target key` error. `dependencies`
and `sign` are accepted only in `[build]` targets; using either in
`test`/`install`/`clean` is an unknown key.

The `dependencies` table is keyed by **provider** (a selector), each mapping
dependency names to version constraints:

```toml
[build.linked.dependencies]
pesb-dev.libc = ">=2.38"
pesb-dev."pkgconfig(zlib)" = "*"
```

| Level | Type | Meaning |
| --- | --- | --- |
| provider | table | A dependency-provider selector. |
| provider.*&lt;name&gt;* | string | Version constraint for the capability *name*. Must be non-empty; use `"*"` for any version. |

Dependency names may be real package names or virtual capabilities (sonames,
`pkgconfig(...)`, etc.) and are validated against peipkg's capability grammar.
See [Commands and targets](/pekit/running/commands-and-targets.md).

#### `[build.<name>.sign]` {#target-sign}

The `sign` table is keyed by **signature kind**. Each kind is a table mapping
a path or glob, relative to the target's `$PEKIT_OUT`, to the dotted path of
the keyring entry holding the signing key:

```toml
[build.main.sign.pip]
"bin/peinit"  = "tcb.priv"
"lib/*.so.*"  = "tcb.priv"
```

The kind decides the signature format and where it is stored; the keyring
entry is resolved through the ordinary [keyring mechanism](/pekit/recipes/environments-and-keyrings.md#keyrings)
when the target runs. Signing happens after the target's command exits 0 and
before any dependent target or package sees the output.

| Level | Type | Meaning |
| --- | --- | --- |
| kind | table | A signature kind. The only kind is `pip` — the Peios binary signature that confers a [Process Integrity Protection](/peios/advanced-peios/pspk/binary-signing-and-pip/process-integrity-protection.md) tier. Any other name is `unknown_key`. |
| kind.*&lt;pattern&gt;* | string | A relative path or glob (the same glob syntax as `[files]`). The value is a keyring leaf path such as `tcb.priv` whose value is the path to the private key. Must be non-empty. |

For `pip`, the key is an ML-DSA-65 private key — PKCS#8 PEM as produced by
`openssl genpkey -algorithm ML-DSA-65`, or a raw 32-byte seed — and every
matched file must be a 64-bit little-endian ELF. A pattern that matches
nothing (`sign_target_missing`), a keyring entry that is absent or does not
load (`signing_key`), a file that cannot carry the signature (`sign_failed`),
and two patterns naming different keys for one file (`sign_conflict`) are all
errors; pekit never leaves a listed file unsigned. The mechanism is described
in [Signing and provenance](/pekit/running/signing-and-provenance.md#signing-binaries-for-pip).

### `[gen]` {#gen}

A `[gen]` section defines **source-generating** targets: they run at the recipe
root and write generated source *into the tree* rather than producing an
`out_dir` artifact. Bare/named shapes work exactly as for the target sections
above (`[gen]` is `gen.main`; `[gen.<name>]` names a target). Gen targets have
their own fields — they take **no** `needs` or `clear_out`:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `command` | string or array of strings | **yes** | The generator. Runs at the recipe root; writes generated source in place. |
| `verify_command` | string or array of strings | no | The drift gate: exit 0 = in sync, non-zero = stale. Run by `pekit verify` and by the consuming-command pre-flight. |
| `verify_on_build` | array of strings | no | Build target names this gate applies to. **Absent** gates every build; **`[]`** gates none; a list gates only those. |
| `verify_on_test` | array of strings | no | As `verify_on_build`, for the `test` namespace. |
| `dependencies` | table | no | Build-time dependencies for `command` (and for `verify_command`, unless overridden). Same shape as a build target's `dependencies`. |
| `verify_dependencies` | table | no | When present, **fully replaces** `dependencies` for the `verify_command` run. |

Any other key is an `unknown target key` error. Setting `verify_on_build`,
`verify_on_test`, or `verify_dependencies` without a `verify_command` is an
`invalid_gen` error (there is nothing to gate).

A gen target's `$PEKIT_OUT` is a pekit-managed scratch directory
(`<out_dir>/.scratch/gen/<name>`) outside the artifact area, cleaned on success
and retained on failure — a convenient place for `verify_command` to regenerate
into for comparison. For the command surface, the pre-flight, and `--no-verify`,
see [Commands and targets](/pekit/running/commands-and-targets.md#generating-source-gen-and-verify).

## `package.pekit.toml`

The package file describes one or more package artifacts to emit from the built
source. The same schema applies to the base file (`package.pekit.toml`) and to
per-member member files (`<name>.package.pekit.toml`); see
[Multi-package recipes](/pekit/recipes/multi-package.md) and
[Supporting files](/pekit/reference/supporting-files.md) for how the files are
discovered and layered.

### Worked example

```toml
format = "peipkg"
builds = ["main"]

[package]
name = "tool"
version = "{{version}}"
architecture = "x86-64-v3"
description = "An example tool"
license = "MIT"
homepage = "https://example.com/tool"
default_root = "opt.tool"

[dependencies]
libc = ">=2.38"
zlib = { constraint = ">=1.3", root = "opt.tool" }

[optional_dependencies]
libcurl = ">=8.0"

[provides]
tool = "{{version}}"

[files]
"main:bin/tool" = "usr/bin/tool"
"@recipe:share/" = { path = "usr/share/tool/", override = true }

[symlinks]
"usr/bin/t" = "tool"

excludes = ["@recipe:share/*.tmp"]

[claims.provides.editor.default]
path = "usr/bin/editor"
target = "usr/bin/tool"

[[publish.localdir]]
path = "dist"
overwrite = false
```

### Top-level keys

The package file accepts exactly these top-level keys; anything else is an
`unknown package key` error.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `format` | string | no | Artifact format: `"tar"` or `"peipkg"`. Default `"tar"`. |
| `clear_out` | bool | no | Wipe the package stage directory before building. Default `true`. |
| `builds` | array of strings | no | Names of build targets this package requires before staging. |
| `package` | table | no | Package metadata. See [`[package]`](#package). |
| `files` | table | no | Payload file mappings. See [`[files]`](#files). |
| `symlinks` | table | no | Symlinks to embed in the payload. See [`[symlinks]`](#symlinks). |
| `excludes` | array of strings | no | Refs/globs to drop from directory payloads. See [`excludes`](#excludes). |
| `multipack` | table | no | Expand into a family of instances. See [`[multipack]`](#multipack). |
| `publish` | table | no | Where to copy finished artifacts. See [`[publish]`](#publish). |
| `dependencies` | table | no | Runtime dependencies. See [`[dependencies]`](#dependencies). |
| `optional_dependencies` | table | no | Optional runtime dependencies (same schema as `dependencies`). |
| `conflicts` | table | no | name → constraint of packages this one conflicts with. |
| `provides` | table | no | name → version of capabilities this package provides. |
| `replaces` | table | no | name → constraint of packages this one replaces. |
| `side_effects` | array of strings | no | Declared install-time side effects. |
| `sd_overrides` | table | no | path → SDDL security-descriptor overrides. |
| `claims` | table | no | Claim slots on provides/dependencies. See [`[claims]`](#claims). |

> [!IMPORTANT]
> The `tar` format carries payload only: it **cannot express manifest
> metadata**. If a `tar` package sets `version`, `architecture`,
> `description`, `license`, `homepage`, or any of `dependencies`,
> `optional_dependencies`, `conflicts`, `provides`, `replaces`,
> `side_effects`, or `sd_overrides`, the build fails with
> `unsupported_manifest_field`. The `peipkg` format requires
> `[package].version`, `[package].architecture`, and `[package].license` —
> the license is optional at the format level, but pekit requires it so every
> package states its redistribution terms and the corresponding-source
> package can derive its own.

Most string-valued fields (metadata, dependency names/constraints, file refs
and destinations, claim paths/targets) are run through the template engine,
where `{{version}}` and `{{multipack}}` are available. See
[Versions](/pekit/recipes/versions.md) and
[Multi-package recipes](/pekit/recipes/multi-package.md).

### `[package]`

Package metadata. Accepts exactly these keys; any other is an
`unknown package metadata key` error. All values are strings.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `name` | string | no | Package name. Defaults to the package selector (suffixed with the multipack value for multipack instances) when omitted. |
| `version` | string | no | Package version. Required for `peipkg`. |
| `architecture` | string | no | Target architecture. Required for `peipkg`. |
| `description` | string | no | Human-readable description. |
| `license` | string | no | License identifier. Required for `peipkg`. |
| `homepage` | string | no | Project homepage URL. |
| `default_root` | string | no | Preferred [named root](/peios/using-peios/package-management/claims.md) for a top-level install. Must be a dotted named reference matching `[a-z0-9][a-z0-9_-]*` per segment, never a filesystem path (a `/` is rejected). |
| `special_system_package` | boolean | no | Declares the package exempt from the payload layout rules, for the rare package whose job is to lay down the structure those rules protect — the base-filesystem package that mints the mountpoint tree is the archetype. Setting it disables pekit's layout validation for this package. It grants nothing at install time: whoever installs or composes the result must *also* pass `--dangerously-bypass-path-restrictions`, so a recipe can propose its own exemption but never grant one. Defaults to `false`. |

Note that `dependencies`, `provides`, `conflicts`, and the rest are **top-level
tables**, not sub-keys of `[package]`.

### `[dependencies]`

Runtime dependency constraints. `[optional_dependencies]` has the identical
schema. Each entry is either a bare constraint string (the short form) or a
table that additionally places the dependency in a named root.

| Form | Type | Meaning |
| --- | --- | --- |
| *&lt;name&gt;* `= "constraint"` | string | Version constraint; the dependency lands in the depender's own root. |
| *&lt;name&gt;* `= { constraint, root }` | table | Table form (below). |

Table form keys — only these two are accepted (`unknown_key` otherwise):

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `constraint` | string | no | Version constraint. Omitting it matches any version. |
| `root` | string | no | Named root to place the dependency in. Same grammar as `default_root`. |

Dependency names may be real or virtual capabilities. See
[Dependencies and claims](/pekit/recipes/dependencies-and-claims.md).

### `[files]`

Maps a **source ref** (the key) to a destination path within the package
payload (the value). The value is either a string destination or a table.

| Value form | Meaning |
| --- | --- |
| `"ref" = "dest/path"` | Copy the ref to `dest/path`. |
| `"ref" = { path, override }` | Table form (below). |

Table form keys — only these are accepted:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `path` | string | **yes** | Destination path in the payload. Must be non-empty. |
| `override` | bool | no | Mark the payload as an override entry (skips manifest payload validation for `peipkg`). Default `false`. |

The source ref selects where the file is read from. A bare path with no prefix
is resolved against the recipe root (or the layer's owner). Explicit forms:
`@recipe:<path>`, `@source:<path>`, `@workspace:<path>`, and
`<build-target>:<path>` (reading a build target's output; `:<path>` alone uses
the `main` build target). Destinations ending in `/` and glob patterns are
supported. See [Packages](/pekit/recipes/packages.md) for ref resolution and glob
semantics.

### `[symlinks]`

Maps a destination path within the payload (the key) to a symlink target (the
value).

| Value form | Meaning |
| --- | --- |
| `"usr/bin/x" = "target"` | Create a symlink at `usr/bin/x` pointing at `target`. |
| `"usr/bin/x" = { target, override }` | Table form (below). |

Table form keys — only these are accepted:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `target` | string | **yes** | Symlink target text. Must be non-empty. |
| `override` | bool | no | Mark as an override entry. Default `false`. |

### `excludes`

An array of refs/globs. During directory payload expansion, files matching an
exclude pattern (resolved against the same root as the file mapping being
expanded) are dropped from the payload. Patterns use the same ref forms as
`[files]` keys.

### `[multipack]`

Expands one definition into a family of package instances, one per enumerated
value. The only accepted key is `enum`, which takes one of two shapes:

**Explicit list:**

```toml
[multipack]
enum = ["gtk3", "gtk4"]
```

Each value must be a valid selector, non-empty, and unique.

**Enumerated from files:**

```toml
[multipack.enum.files]
path = "@source:themes/*/theme.ini"
regex = "^(?P<value>.+)$"
```

`[multipack.enum.files]` accepts exactly `path` and `regex`, both **required**:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `path` | string | **yes** | A ref glob (templated) whose matches are enumerated. |
| `regex` | string | **yes** | Regex applied to each match's basename. Must have exactly one capture group, or a single named `value` group, which supplies the instance value. |

See [Multi-package recipes](/pekit/recipes/multi-package.md).

### `[publish]`

Where finished artifacts are copied. The only accepted target is `localdir`,
written as an **array of tables**:

```toml
[[publish.localdir]]
path = "dist"
overwrite = false
```

Each entry accepts only `path` and `overwrite`:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `path` | string | **yes** | Destination directory, relative to the workspace root (or recipe root outside a workspace). Templated. The artifact keeps its own filename. |
| `overwrite` | bool | no | Allow replacing an existing file at the destination. Default `true`. |

### `[claims]`

Attaches **claim** slots to provides and dependency entries (a claim is a
symlink slot a provider fills or a consumer expects). The `[claims]` table has
two sides — `provides` and `dependencies` — each keyed by the provides or
dependency name, then by slot, then to a descriptor. Any side other than
`provides`/`dependencies` is an `unknown_key` error.

```toml
[claims.provides.<name>.<slot>]
path = "usr/bin/editor"
target = "usr/bin/tool"

[claims.dependencies.<name>.<slot>]
path = "usr/bin/editor"
```

A slot descriptor accepts exactly these two keys (anything else is rejected);
both are strings and both are templated:

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `path` | string | no | The symlink location (set on a consumer, or as a provider default). |
| `target` | string | no | The holder file within this package (set on a provider). A provider's target must point at a file the package ships. |

Provider-side slots (`claims.provides`) attach to matching `[provides]`
entries by name; consumer-side slots (`claims.dependencies`) attach to matching
`[dependencies]`/`[optional_dependencies]` entries. See
[Dependencies and claims](/pekit/recipes/dependencies-and-claims.md) and the Peios
[claims](/peios/using-peios/package-management/claims.md) model for the resolution behaviour.

## See also

- [Supporting files](/pekit/reference/supporting-files.md) — the workspace, env, keyring, lockfile, and patch-series schemas.
- [Anatomy of a recipe](/pekit/recipes/anatomy.md) — the narrative walk-through of these files.
- [Packaging files](/pekit/recipes/packages.md) — how `[files]` refs and destinations resolve.

---

# Supporting files and reference tables

_pekit / Reference_

> Exact schemas for pekit's non-recipe files (workspace, env, keyring, pekit.lock, patch series) plus template variables, source-ref roots, and publish targets.

This page documents the TOML files that sit *alongside* a recipe — the
workspace file, env files, and keyring files — and gives exhaustive reference
tables for the three substitution systems used throughout pekit: template
variables, source-ref roots, and publish targets.

All files are TOML. Every loader rejects unknown top-level keys with an
`unknown_key` diagnostic, so the key tables below are complete: any key not
listed is an error.

For the recipe file itself (`pekit.toml` / `*.pekit.toml`), see
[recipe-format](/pekit/reference/recipe-format.md).

---

## `workspace.pekit.toml`

A workspace file marks a directory as a pekit workspace and declares
distro-wide defaults.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `include` | array of strings | **yes** | Member patterns the workspace builds. Must be present and non-empty, or the load fails (`missing_key`). |
| `exclude` | array of strings | no | Patterns removed from the `include` set. |
| `[env]` | table | no | Workspace-level environment variables. See [env table](#env-table). |
| `[wrap]` | table | no | Workspace-level command wrapper. See [wrap table](#wrap-table). |
| `[policy]` | table | no | Distro-wide derivation policy. See below. |

### `[policy]`

The `[policy]` table currently carries exactly one sub-table. Any other key
under `[policy]` is rejected.

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `[policy.symbol_versions]` | table (string → string) | no | Maps a shared-library soname to the symbol-version **token prefix** whose tokens are commensurable with the providing package's version. Governs which sonames get a symbol-version floor during dependency derivation. |

Each entry in `[policy.symbol_versions]` is `soname = "PREFIX_"`:

```toml
[policy.symbol_versions]
"libc.so.6"     = "GLIBC_"
"libstdc++.so.6" = "GLIBCXX_"
```

---

## `env.pekit.toml`

An env file provides a *named, selectable* layer of environment variables,
command wrapper, and dependency-provider override.

An env file must declare **at least one** of `[env]`, `[wrap]`, or
`dependency_provider`; a file with none of them is rejected (`missing_key`).

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `[env]` | table | one of the three | Environment variables. See [env table](#env-table). |
| `[wrap]` | table | one of the three | Command wrapper. See [wrap table](#wrap-table). |
| `dependency_provider` | string | one of the three | Selector naming which of a build target's declared dependency-provider blocks (`[build.<target>.dependencies.<provider>]`) is exported as the `PEKIT_DEPENDENCIES*` variables. Validated as a [selector](#selectors). |

### `--env <name>` selection

The active env file is chosen from the `--env <name>` flag (default `main`),
resolved relative to the recipe root:

| `--env` value | File loaded | Must exist? |
| --- | --- | --- |
| *(unset)* or `main` | `env.pekit.toml` | no (silently absent) |
| `none` | *(no env file loaded)* | n/a |
| any other `<name>` | `<name>.env.pekit.toml` | **yes** — a missing file is an error |

So `--env prod` loads `prod.env.pekit.toml`, and it must exist. The default
`env.pekit.toml` is optional.

### `[env]` table

Shared by `workspace.pekit.toml`, `env.pekit.toml`, and the recipe's own
`[env]`. It is a flat table of `NAME = "value"` string pairs.

| Rule | Detail |
| --- | --- |
| Value type | Every value must be a string. |
| Name grammar | Names must match `^[A-Za-z_][A-Za-z0-9_]*$`. |
| Reserved prefix | User env may not set any `PEKIT_*` name — those are pekit-managed. A `PEKIT_`-prefixed name is rejected at build time (`reserved_env`). |
| Layering | Applied workspace → (delegated source) → recipe → env file, later layers overriding earlier ones. |
| Expansion | Values are shell expressions, not literals. `$NAME` expands when the target runs, so a value may reference a managed `PEKIT_*` variable or an env value declared before it. Values are word-split-safe: spaces are preserved, so a flag list needs no quoting of its own. |

```toml
[env]
CC = "clang"
CFLAGS = "-O2 -pipe"
CXXFLAGS = "$CFLAGS -Wp,-D_GLIBCXX_ASSERTIONS"
TOOL_PATH = "$PEKIT_ROOT/tools"
```

`$` always expands — a literal `$` is not expressible here, and `\$` does not
escape it. A value needing one (an `$ORIGIN` rpath, say) belongs in the target's
`command`, where the recipe has the full shell.

### `[wrap]` table

Shared by `workspace.pekit.toml`, `env.pekit.toml`, and the recipe. Wraps every
target command in another command (sandbox, `nice`, a toolchain shim, etc.).

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `command` | string or array of strings | no | The wrapper. Must contain exactly one `{{command}}` placeholder. |

`{{command}}` rules:

- **String form** — the string must contain `{{command}}` exactly once.
- **Array form** — exactly one element must be the literal `{{command}}` (as a
  *complete* argument, not a substring), and it may not be element `0` (the
  program). An element that merely contains `{{command}}` inside other text is
  rejected.

```toml
[wrap]
command = ["bwrap", "--ro-bind", "/usr", "/usr", "{{command}}"]
```

Note: `{{command}}` here is a wrap-only placeholder handled by the wrap
machinery. It is **not** one of the [template variables](#template-variables)
below and is not available in ordinary recipe strings.

---

## `*.keyring.pekit.toml`

A keyring file supplies secrets to the build environment as `PEKIT_KEYRING_*`
variables. Keyrings are opt-in per invocation (`--keyring`); a keyring named
`<name>` resolves to `<name>.keyring.pekit.toml`, searched in the workspace
root then the recipe root (a path-like value is used verbatim).

### Schema

The file is a tree of nested TOML tables whose **leaves are strings**. Each
leaf's dotted path is converted into an environment-variable name:

- Prefix `PEKIT_KEYRING_`, then the dotted path uppercased.
- Every run of non-alphanumeric characters (including the `.` separators)
  collapses to a single `_`.
- Trailing `_` is trimmed.

| Leaf path | Exported variable |
| --- | --- |
| `token` | `PEKIT_KEYRING_TOKEN` |
| `github.token` | `PEKIT_KEYRING_GITHUB_TOKEN` |
| `registry.api-key` | `PEKIT_KEYRING_REGISTRY_API_KEY` |

```toml
# placeholders only — never commit real secrets
token = "REPLACE_ME"

[github]
token = "REPLACE_ME"

[registry]
api-key = "REPLACE_ME"
```

### Restrictions

| Rule | Detail |
| --- | --- |
| Leaf type | Every leaf must be a string. A non-string leaf is an error. |
| Nesting | Sub-tables nest arbitrarily; each nesting level adds a segment to the exported name. |
| Typed entries | A table containing a `path` or `content` key is a *typed* entry and is **not supported yet** (`unsupported_keyring_entry`). |
| Collisions | A keyring export that collides with a normal or managed env variable is an error (`env_collision`). |

### Well-known entries

Most keyring values are opaque to pekit — they exist to be exported. The
entries below are additionally read by pekit itself:

| Leaf path | Meaning |
| --- | --- |
| `signing.package_key` | Path to an Ed25519 private key — a raw 32-byte seed, or PKCS#8 PEM as produced by `openssl genpkey -algorithm ed25519`. When present, every peipkg-format artifact the run produces — package members and `-source` packages alike — is packed with an embedded signature naming the key's fingerprint, and each emits a `sign` event. A relative path resolves against the invocation's working directory. A configured key that cannot be loaded is an error (`signing_key`); pekit never silently falls back to unsigned output. |

Without a configured signing key, `package` writes unsigned artifacts, and
`publish` refuses peipkg-format packages unless `--allow-unsigned` is passed
(`unsigned_publish`).

A build target's [`sign` table](/pekit/reference/recipe-format.md#target-sign)
reads further entries, but which ones is the recipe's choice: each `sign.pip`
value is the dotted path of a leaf holding the path to an ML-DSA-65 private
key. Those entries are still exported to the target like any other.

---

## `pekit.lock`

The machine-written source lockfile, created and updated by pekit in the recipe
directory beside `pekit.toml`. It pins fetched source inputs trust-on-first-use:
the first resolve of a version records what was fetched, and every later
resolve verifies against the record — see
[Sources](/pekit/recipes/sources.md#the-lockfile) for the behaviour. Commit it,
and do not edit it by hand: `pekit lock --repin --version <v>` is the one
supported way to change an existing entry. Loading is strict — an unknown key
is `unknown_key` and an unsupported `schema` is `lock_schema`.

### Schema

| Key | Type | Meaning |
| --- | --- | --- |
| `schema` | integer | Lockfile schema version. Currently `1`. |
| `[[source]]` | array of tables | One entry per locked version, kept sorted by version. |

Each `[[source]]` entry carries the keys for its source kind:

| Key | Set for | Meaning |
| --- | --- | --- |
| `version` | all | The locked version. Empty for a versionless url fetch. |
| `url` | url sources | The rendered URL fetched at lock time. Provenance only — the hash, not the address, is the assertion. |
| `sha256` | url sources | SHA-256 of the fetched artifact. |
| `ref` | git sources | The rendered ref the version resolved through. |
| `commit` | git sources | The commit the ref resolved to — the assertion. |
| `signature_key` | url sources with `[source.url.signature]` | Hex fingerprint of the pinned upstream key that verified the artifact at lock time. |
| `locked_at` | all | UTC timestamp of the pinning run (RFC 3339). |

### Worked example

```toml
schema = 1

[[source]]
  version = "0.5.12"
  url = "https://example.org/dash-0.5.12.tar.gz"
  sha256 = "6a474ac46e8b0b32916c4c60df694c82058d3297d8b385b74508030ca4a8f28a"
  signature_key = "9f0b7ddc1325a3e2c40e5f0a88b1c62f3d94ab07"
  locked_at = "2026-08-10T11:00:00Z"

[[source]]
  version = "1.2.0"
  ref = "v1.2.0"
  commit = "8f3a1bc99e2f6d41c07b21ac1886d63b7f2e5d10"
  locked_at = "2026-08-10T11:00:00Z"
```

---

## The patches directory

The directory named by `[source] patches = "<dir>"` in `pekit.toml`
(conventionally `patches`, and always a single path segment). pekit applies
its series to the materialised source tree before any target runs — see
[Sources](/pekit/recipes/sources.md#patches) for the behaviour — and the whole
directory ships in the recipe's source package under `patches/`.

### The series file

`<dir>/series` lists the patches in apply order: one slash-separated relative
path per line. `#` starts a comment, whole-line or trailing; blank lines are
ignored; entries must not contain whitespace. Subdirectories are allowed
(`misc/fix.patch`).

Loading is strict:

| Condition | Error |
| --- | --- |
| `series` missing or unreadable | `patch_series` |
| An entry that repeats, contains whitespace, or escapes the directory | `patch_series` |
| A series entry with no file behind it | `patch_missing` |
| A `*.patch` file in the directory the series does not list | `unused_patch` (permitted by `--allow-unused`) |

Application is `git apply` per entry, in series order, with no fuzz: a failed
hunk is `patch_apply` and a patch that matches nothing is `patch_skipped`.

### Worked example

```
patches/
  series
  misc/
    mke2fs-root-sd.patch
```

with a `series` of:

```
# Applied in dependency order:
misc/mke2fs-root-sd.patch   # stamp the root inode's SD at format time
```

Give each patch file a short header — what it changes and its upstream status
(submitted, backport, Peios-specific). The series ships with the
corresponding source, where that header is a third party's only context.

---

## Template variables

Recipe and package **value strings** (source refs, `ref`, package metadata,
file/symlink paths, excludes, claims, multipack `enum.files.path`, publish
`path`) are run through the template engine before use. The syntax is
`{{name}}`, where `name` matches `[A-Za-z0-9_]+`. An unknown name is an error.

| Variable | Source | Availability |
| --- | --- | --- |
| `{{version}}` | the selected version, verbatim (`Raw`) | Errors when no version is selected. |
| `{{major}}` | major component | Available once a version is selected. |
| `{{minor}}` | minor component | Errors if the selected version has no minor component. |
| `{{patch}}` | patch component | Errors if the selected version has no patch component. |
| `{{prerelease}}` | pre-release component (after `-`) | Empty string when absent. |
| `{{buildmeta}}` | build-metadata component (after `+`) | Empty string when absent. |
| `{{multipack}}` | the current multipack instance value | Only available while expanding a multipack package's per-instance values; errors in any non-multipack context. |

A version string parses as `major[.minor[.patch]][-prerelease][+buildmeta]`, so
`{{minor}}` / `{{patch}}` are only present when the version actually carries
them.

### Shell targets use `$PEKIT_*` instead

The `{{...}}` engine applies to recipe *configuration* strings, not to the shell
bodies of build/test/install/clean commands. Target commands instead read the
pekit-managed environment. The version equivalents are:

| Template variable | Environment variable |
| --- | --- |
| `{{version}}` | `$PEKIT_VERSION` |
| `{{major}}` | `$PEKIT_VERSION_MAJOR` |
| `{{minor}}` | `$PEKIT_VERSION_MINOR` |
| `{{patch}}` | `$PEKIT_VERSION_PATCH` |
| `{{prerelease}}` | `$PEKIT_VERSION_PRERELEASE` |
| `{{buildmeta}}` | `$PEKIT_VERSION_BUILDMETA` |

Alongside these, the managed environment also exports `PEKIT_RECIPE_ROOT`,
`PEKIT_SOURCE_ROOT`, `PEKIT_LITERAL_ROOT`, `PEKIT_ROOT`, `PEKIT_OUT_BASE`,
`PEKIT_OUT`, `PEKIT_COMMAND`, `PEKIT_TARGET`, `PEKIT_WORKSPACE_ROOT`,
`PEKIT_BUILD_TIMESTAMP`, `PEKIT_SOURCE_TIMESTAMP`, a `PEKIT_<DEP>_OUT` per
declared `needs` dependency, and any `PEKIT_KEYRING_*` from active keyrings. The
full environment contract is documented in the
[command-line reference](/pekit/reference/cli.md).

---

## Source-ref roots

Package `files` sources, `excludes`, and `multipack.enum.files.path` are
*references* whose leading token selects which root the path is resolved
against. A reference is `ROOT:path` (or a bare path, which normalises to the
owner's default root).

| Reference form | Root | Meaning |
| --- | --- | --- |
| `target:path` | the build target's output stage | Reads from the output of build target `target`. |
| `:path` | build target `main`'s output stage | Same as above with the target name defaulted to `main`. |
| `@source:path` | the materialised source tree (`LiteralRoot`) | Reads from the checked-out / extracted source. |
| `@recipe:path` | the recipe directory | Reads a file shipped next to the recipe. |
| `@workspace:path` | the workspace root | Reads a file from the workspace. Errors if used outside a workspace. |
| bare `path` | owner default | A path with no `:` normalises to the owning layer's root: `@recipe:` for recipe packages, `@source:` for source packages, `@workspace:` for workspace packages. |

Any reference containing a `:` that is not one of the `@`-prefixed roots is a
**build** reference; the token before the `:` is the target name. See
[packages](/pekit/recipes/packages.md) for how these are used in a package's
`[files]` table.

---

## Publish targets

The `publish` command copies a package's built artifact to declared destinations. There is
exactly one publish kind: `localdir`. Any other `[publish.*]` key is rejected
(`unknown_key`). A package selected for publishing that declares no target is an
error.

Targets are an array of tables:

```toml
[[publish.localdir]]
path = "dist/{{version}}"
overwrite = false
```

| Key | Type | Required | Meaning |
| --- | --- | --- | --- |
| `path` | string | **yes** | Destination **directory**, relative to the base root. Templated with `{{version}}` and `{{multipack}}`. The artifact keeps its own filename. |
| `overwrite` | bool | no (default `true`) | Whether an existing destination file may be replaced. When `false`, an existing destination is an error (`publish_exists`). |

Resolution details:

| Aspect | Behaviour |
| --- | --- |
| Base root | The workspace root when building in a workspace, otherwise the recipe root. `path` is joined onto it. |
| Final destination | `<base>/<path>/<artifact-filename>`. |
| Collisions | Two instances resolving to the same destination is an error (`publish_collision`) unless they are the same artifact. |

<a id="selectors"></a>

## Selectors

Several names above are validated as **selectors**: target names, package
selectors, multipack values, and `dependency_provider`. A selector must match
`^[A-Za-z0-9_.+-]+$`, must not be empty, must not start with `-`, and must not
contain `/` or `:`.

## See also

- [Recipe format reference](/pekit/reference/recipe-format.md) — the `pekit.toml` and package-file schemas.
- [Environments and keyrings](/pekit/recipes/environments-and-keyrings.md) — how env and keyring files are selected and layered.
- [Signing and provenance](/pekit/running/signing-and-provenance.md) — `signing.package_key` and `pekit.lock` in the trust chain.

---

# Command-line reference

_pekit / Reference_

> Every pekit flag with its value form, the command-by-flag capability matrix, validation rules, exit codes, and the full PEKIT_* environment contract.

This is the exhaustive reference for pekit's command line and the environment it
exports to the commands it runs. Every flag listed here is a real flag accepted
by the parser; there are no others. For a narrative walk-through of the grammar
and recipe location, see [Invocation and flags](/pekit/running/invocation.md);
for what each command does, see
[Commands and targets](/pekit/running/commands-and-targets.md).

## Synopsis

```text
pekit [global flags] <command> [args and flags]
pekit [global flags] workspace [workspace flags] <command> [args and flags]
```

`<command>` is exactly one of `build`, `test`, `install`, `package`, `publish`,
`clean`, `gen`, `verify`, `lock`, or `workspace`. Flags may appear before or after the command word and
are parsed identically on both sides — the sole exception is the `workspace` tail
(see [Workspace flags](#workspace-flags)). A bare `--` ends flag parsing; every
following token is captured verbatim as a positional selector. Positional
selectors may not begin with `-`.

There is **no `--help` and no `help` command**, and no version banner: `-V` is an
alias for the `--version` *selector*, not a print-version flag.

## Flag value forms

The shape of a flag determines how its value may be written.

| Shape | How a value is written |
|---|---|
| No-value | Never takes a value. `--flag=x` errors with `unexpected_flag_value`. |
| Required-value | `--flag value` **or** `--flag=value`. A missing next token, a next token that is `--` or begins with `-`, or an empty `--flag=` all error with `missing_flag_value`. |
| Optional-value | Value **only** via `--flag=value`. The bare `--flag` is valid and means an empty value; it never consumes the following token. |

## Global flags

Accepted by every command, before or after the command word. These are never
subject to the capability matrix below.

| Flag | Value form | Semantics |
|---|---|---|
| `--recipe <path\|dir>` | Required-value | Use this recipe instead of searching upward from the cwd. A directory means `<dir>/pekit.toml`. |
| `--workspace <path\|dir>` | Required-value | Use this workspace file/root. Valid **only** with the `workspace` command. |
| `--allow-unused` | No-value | Downgrade "command does not support this recognised flag" from a fatal error to a suppressed notice. |
| `--dry-run` | No-value | Run the full planning pipeline but stop before any side effect; emit plan events instead. |
| `--quiet` | No-value | Human renderer, routine progress suppressed (only `warning`, `artifact`, `publish`, `workspace_summary`, `lock`, `sign` events). |
| `--verbose` | No-value | Human renderer plus extra staging/progress events. |
| `--json` | No-value | Emit newline-delimited JSON events on stdout instead of the human log. |

The renderer is chosen from these flags: `--json` selects the JSON renderer
(with `--dry-run` it buffers and prints a single `plan` object); otherwise the
human renderer runs, in quiet mode when `--quiet` is set.

## Version, source, and build flags

These are command-specific: each is only accepted by the commands marked in the
[capability matrix](#capability-matrix). Passing one to a command that does not
support it is `unsupported_flag` unless `--allow-unused` is set.

| Flag | Value form | Meaning |
|---|---|---|
| `--version <v>`, `-V <v>` | Required-value | Select a single recipe version. `-V` is an exact alias. |
| `--latest` | No-value | Select only the newest available version. |
| `--all-versions` | No-value | Act on every available version. |
| `--local[=<path>]` | Optional-value | Use a local source tree instead of resolving source; bare form uses the default, `=<path>` overrides it. |
| `--prefer-local[=<path>]` | Optional-value | Use the local source when present, otherwise resolve normally. |
| `--no-build[=<list>]` | Optional-value | Reuse already-staged build targets. Bare form reuses all; `=a,b,c` reuses only the named build targets. Only affects `build`-kind targets whose stage already exists. |
| `--no-verify[=<list>]` | Optional-value | Skip the [gen drift-check pre-flight](/pekit/running/commands-and-targets.md#generating-source-gen-and-verify). Bare form skips all; `=a,b,c` skips only the named gen targets (an unknown name errors). On `package`/`publish` it passes through to the builds they trigger. |
| `--env <name>` | Required-value | Select the environment file layer. `main` (default) loads `env.pekit.toml`; `<name>` loads `<name>.env.pekit.toml`; `none` loads no env file. |
| `--keyring <name\|path>` | Required-value | Load a keyring file. A bare name resolves to `<name>.keyring.pekit.toml` searched in the workspace root then recipe root; a path-like value is used directly. Repeatable. |
| `--keyring.<path>=<value>` | Dotted, `=` required | Set a single keyring value inline. Requires a non-empty dotted path and the `=`; otherwise `invalid_keyring_flag`. Repeatable. |
| `--refresh-source` | No-value | Force source re-materialisation, ignoring any source cache. The [lockfile](/pekit/reference/supporting-files.md#pekitlock) is still enforced against the fresh download. |
| `--allow-unanchored` | No-value | Permit `publish` from source with unanchored provenance — no checksum **and** no lock entry (otherwise `unanchored_provenance`). |
| `--allow-unsigned` | No-value | Permit `publish` of peipkg-format packages without a configured [signing key](/pekit/reference/supporting-files.md#well-known-entries) (otherwise `unsigned_publish`). |
| `--repin` | No-value | `lock` only: re-download the selected version and **replace** its lock entry — the explicit accept-changed-upstream-bytes ceremony. Requires an exact single `--version`. |
| `--all` | No-value | `package`/`publish`: act on every package definition instead of a selector. `gen`/`verify`: act on every gen target. |
| `--output-only` | No-value | `clean`: remove managed output only, without running a clean target. |
| `--target-only` | No-value | `clean`: run the clean target only, without removing managed output. |

`--version`/`-V`, `--latest`, and `--all-versions` form one mutually-exclusive
group ("version selection"); `--local` and `--prefer-local` form the
"local source flags" group.

## Workspace flags

`workspace` delegates to another command. Its tail is parsed specially: the two
workspace-only flags must appear **after** `workspace` and **before** the
delegated command word. Everything after the delegated command is parsed as an
ordinary invocation of that command.

| Flag | Value form | Meaning |
|---|---|---|
| `--jobs <N>`, `--jobs=<N>` | Required-value, positive integer | Maximum concurrent members. Default `1`. A non-integer or value `<= 0` errors with `invalid_flag_value`. |
| `--fail-fast` | No-value | Abort the whole workspace run on the first member failure. |

Global flags (`--dry-run`, `--json`, `--recipe`, …) may also appear in the tail
before the delegated command. A **version/source/build** flag placed there is an
error (`missing_workspace_command`, *"workspace command flag … must appear after
the delegated command"*): those belong after the delegated command. The same
code fires when no delegated command follows at all. See
[Workspaces](/pekit/running/workspaces.md).

## Capability matrix

Which command accepts which flag group. Global flags and `workspace` flags are
omitted (global flags are accepted everywhere). A blank cell means the flag group
is unsupported for that command.

| Command | version selection | local source | `--no-build` | `--no-verify` | `--env` | `--keyring` | `--refresh-source` | `--allow-unanchored` | `--allow-unsigned` | `--all` | clean mode | `--repin` |
|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| `build` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | | | | |
| `test` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | | | | |
| `install` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | | | | |
| `package` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | | Yes | | |
| `publish` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | |
| `clean` | | | | | Yes | Yes | | | | | Yes | |
| `gen` | | | | | Yes | Yes | | | | Yes | | |
| `verify` | | | | | Yes | Yes | | | | Yes | | |
| `lock` | Yes | | | | | | Yes | | | | | Yes |

"clean mode" is the `--output-only` / `--target-only` pair.

## Validation and mutual-exclusion rules

The parser rejects contradictory or malformed invocations before doing any work.

| Rejected condition | Diagnostic |
|---|---|
| `--quiet` with `--verbose` | `--quiet and --verbose cannot be used together` |
| `--quiet` with `--json` | `--quiet and --json cannot be used together` |
| `--recipe` with `--workspace` | `--recipe and --workspace cannot be used together` |
| `--workspace` without the `workspace` command | `--workspace can only be used with the workspace command` |
| No command word | `missing_command` |
| More than one of `--version` / `--latest` / `--all-versions` | mutually exclusive |
| `--local` with `--prefer-local` | mutually exclusive |
| `--output-only` with `--target-only` | mutually exclusive |
| A recognised flag the effective command does not support | `unsupported_flag` (suppressed under `--allow-unused`) |
| `clean --output-only` with `--env` or `--keyring` | *"clean --output-only does not run a target, so … is unused"* (suppressed under `--allow-unused`) |
| `clean` with more than one selector | `invalid_selector` (clean accepts at most one) |
| `package`/`publish` with `--all` **and** a selector | *"--all cannot be combined with package selectors"* |
| `gen`/`verify` with `--all` **and** a selector | *"--all cannot be combined with gen/verify target selectors"* |
| `lock` with any selector | `invalid_selector` (lock does not accept selectors) |
| `lock --repin` without an exact single `--version` | `invalid_flags` (`--repin requires exactly one exact --version`) |
| `--no-verify` naming an unknown gen target | `missing_target` |
| A positional selector beginning with `-` | `invalid_selector` (use `--` before selector-like values) |

`--allow-unused` relaxes **only** the "recognised-but-unused" and
"clean `--output-only` unused env/keyring" checks; it never excuses unknown
flags, malformed values, the mutual-exclusion rules, or bad selectors.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success. |
| `1` | Any error — bad invocation, missing recipe, failed target, and so on. |

There are no other exit codes.

## Environment contract

Before running a target's command, pekit constructs its environment in layers:
managed `PEKIT_*` variables, then keyring exports, then user-declared env
(workspace → source → recipe → env-file). The process inherits the parent
environment with these overlaid. User env **may not** define any `PEKIT_*`
variable — doing so is `reserved_env`. The command runs with its working
directory set to the source root (or the recipe root for a `clean` target).

### Always set

Exported for every target pekit runs.

| Variable | Value |
|---|---|
| `PEKIT_RECIPE_ROOT` | Directory containing the resolved recipe (`pekit.toml`). |
| `PEKIT_SOURCE_ROOT` | Root of the materialised source tree. Equals the recipe root for a non-delegated recipe. |
| `PEKIT_LITERAL_ROOT` | Root used to resolve `@source:` payload references. |
| `PEKIT_ROOT` | Per-source pekit work base (staging lives beneath it). |
| `PEKIT_OUT_BASE` | Base directory for this source's managed output. |
| `PEKIT_OUT` | This target's own stage directory (where it should write). |
| `PEKIT_COMMAND` | The command kind running the target (`build`, `test`, `install`, `clean`, `gen`, `verify`). |
| `PEKIT_TARGET` | The target's name. |
| `PEKIT_BUILD_TIMESTAMP` | Run start time, Unix seconds. |
| `PEKIT_SOURCE_TIMESTAMP` | Source provenance time, Unix seconds. |
| `PEKIT_WORKSPACE_ROOT` | Workspace root, or an **empty string** when not in a workspace (always set). |
| `PEKIT_DEPENDENCIES_FILE` | Path to the JSON dependency payload pekit writes for this target. |
| `PEKIT_DEPENDENCY_PROVIDER` | Selected env dependency provider (empty when none). |
| `PEKIT_DEPENDENCIES` | Resolved dependencies as `name constraint` lines, one per line (empty when none). |

### Set only when a version is selected

Exported only when the run resolves a non-empty version.

| Variable | Value |
|---|---|
| `PEKIT_VERSION` | Full version string. |
| `PEKIT_VERSION_MAJOR` | Major component. |
| `PEKIT_VERSION_MINOR` | Minor component. |
| `PEKIT_VERSION_PATCH` | Patch component. |
| `PEKIT_VERSION_PRERELEASE` | Pre-release component (may be empty). |
| `PEKIT_VERSION_BUILDMETA` | Build-metadata component (may be empty). |

### Per-dependency and per-keyring variables

| Variable pattern | When set | Value |
|---|---|---|
| `PEKIT_<NEED>_OUT` | One per entry in the target's `needs` | Stage directory of that needed build target. `<NEED>` is the target name upper-cased with `-` and `.` replaced by `_`. Two needs mapping to the same name is `env_collision`. |
| `PEKIT_KEYRING_<PATH>` | One per keyring leaf loaded via `--keyring` / `--keyring.<path>=<value>` | The keyring value. `<PATH>` is the dotted keyring path upper-cased, with each run of non-alphanumeric characters collapsed to a single `_` and trailing `_` trimmed. For example `--keyring.token=…` exports `PEKIT_KEYRING_TOKEN`, and a nested `[registry] token = …` exports `PEKIT_KEYRING_REGISTRY_TOKEN`. |

A keyring export that collides with a managed `PEKIT_*` variable or with a normal
env variable is `env_collision`.

## See also

- [Invocation and flags](/pekit/running/invocation.md) — the narrative walk-through of the grammar.
- [Commands and targets](/pekit/running/commands-and-targets.md) — what each command selects and runs.
- [Environments and keyrings](/pekit/recipes/environments-and-keyrings.md) — the layers behind the environment contract.
