# 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). ## 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 `#