# Universal resolution > [!WARNING] > Universal mode runs the same resolver as a specific resolve. The > multi-target PEP 751 lockfile format it produces is experimental > and may change without notice. A specific resolve pins one version per package for one marker environment. A universal resolve produces a single artefact valid for a set of marker environments. nab's universal-resolution model is user-driven: the user declares their target Python range and platform list in `[tool.nab.matrix]`, and the resolver only ever considers what they declared. ## How it works A matrix expands into a list of resolve targets, one target per `(python, platform, implementation)` point it names. nab resolves the targets one at a time, each on the same engine and the same single-environment resolve a project without a matrix runs once for the host. The targets share one fetcher, so each package's metadata is fetched at most once across the whole matrix. After a target resolves, its pins flow forward as preferences for the next, giving best-effort alignment across targets. The lock a matrix produces is the lock a single environment produces, with more environments in it: the same shape, the same [environment declarations](../reference/lockfile.md), and the same dependency edges. A target whose minor a marker splits contributes one declaration per slice (see Patch-release markers below). ## Where a version's metadata comes from nab reads a version's dependency metadata from the one wheel its target's tags rank most preferred (most specific tag, then highest build tag) and treats it as authoritative for that version on that target. Per-target tag filtering already keeps cross-platform wheels apart, so this is exact wherever the installer's own rules can rank a version's wheels. When a version's wheels tie for a target and the siblings already fetched declare different dependencies, nab reports an error rather than pick one by guessing. It compares only the siblings in hand, so it does not promise to catch every such case. ## Declaring the matrix ```toml [project] name = "example-lib" version = "0.1.0" requires-python = ">=3.11,<3.14" dependencies = [ "numpy", "fastapi", ] [tool.nab] mode = "universal" [tool.nab.matrix] python = ">=3.11,<3.14" platforms = ["linux_x86_64", "macos_arm64"] python-order = "asc" ``` `python` is a PEP 440 specifier expanded into one target per minor version. `platforms` is a list of platform ids (`linux_x86_64`, `linux_aarch64`, `linux_i686`, `linux_armv7l`, `macos_x86_64`, `macos_arm64`, `windows_amd64`, `windows_arm64`), each optionally written as a table to declare its wheel-tag knobs (libc family, the libc and macOS the lock must run on, free-threaded build). See [Configuration](../reference/configuration.md). `python-order` selects the resolution direction: * `"asc"` (default): oldest Python first. Pins propagate forward as preferences; the lowest common version usually wins. Mirrors uv's `fork-strategy=fewest`. * `"desc"`: newest Python first. Pins propagate backward; older Pythons diverge only when the new pin is incompatible. Mirrors uv's `fork-strategy=requires-python`. ## Run ```bash nab lock pyproject.toml ``` Writes a single PEP 751 `pylock.toml` covering the whole matrix. Packages whose pinned version differs across targets appear as multiple `Package` entries with PEP 508 markers; packages that agree across every target appear once with no marker. ## Inspect the per-target pins ```bash nab lock --format requirements-without-hashes --output - pyproject.toml ``` ``` warning: the multi-target ('universal') lockfile format is experimental and may change without notice # py311-linux_x86_64 fastapi==0.115.2 numpy==2.0.2 ... # py311-macos_arm64 fastapi==0.115.2 numpy==2.0.2 ... # py312-linux_x86_64 fastapi==0.115.2 numpy==2.1.3 ... ``` One block per target. Pip cannot install a single requirements.txt across multiple targets in hash-checking mode, so the per-target block format is for inspection or for tools that consume one block at a time. When a target fails resolution, the line reads `#