# Dependency and repository pinning
These rules are part of the [engineering baseline](../DRAGGINZGAME.md).
Use one consistent selection method across repositories; consumers still own
their reviewed versions. Different qualified versions are not automatically drift.
The [Cargo ownership rules](cargo-dependencies.md) define where declarations live.
## Selection and reproducibility
| Registry dependencies | Explicit compatible version requirements plus a tracked lockfile for each maintained build workspace. |
| Git dependencies | Repository URL and full commit ID in `rev`; no branch, tag, abbreviated SHA or implicit default branch as the pin. |
| External Actions and reusable workflows | Full commit SHA in `uses`; a version comment may explain it. Local actions belong to the checked-out source. |
| Additional repository checkouts | Full commit ID in `ref`, unless an approved moving-input exception applies. The workflow's own checkout may follow the selected event/branch/PR. |
| CI container images and Docker actions | `@sha256:` image digest. A repository-owned Dockerfile belongs to the source; review its base-image pins too. |
| Downloaded executables | Exact version and platform checksum, verified before extraction/execution, followed by a version check. |
| Shared Tooling snapshots | Reviewed source commit plus file digests under the [snapshot contract](../docs/consuming-snapshots.md). |
- A Cargo declaration such as `version = "0.49.2"` is a compatibility range,
not an exact pin. Prefer Cargo's ordinary compatible requirements for registry
libraries. Avoid wildcard requirements. Use `=X.Y.Z` only for a concrete
compatibility constraint, such as a coupled macro/runtime API, a qualified
protocol boundary or an upstream regression; record the reason and exact value.
Do not exact-pin every library merely to reproduce a build.
- Track each build workspace's lockfile, including maintained test/integration
workspaces. CI, release validation and artifact-producing Cargo commands use
`--locked` (or `--frozen` when offline operation is intended). Explicit cache
preparation preserves that lockfile. Do not regenerate a lockfile to make a
gate pass. Published libraries still declare compatibility for their consumers;
their own lockfile does not constrain a downstream application's resolution.
- Verify that a selected Git SHA belongs to the intended upstream and review its
change before updating it. A full SHA fixes identity; it does not prove trust,
compatibility or validation. A tag may be recorded as a human-readable label.
- Keep a pin with its existing authoritative owner: root Cargo catalog,
workflow reference, tool-version/checksum file or snapshot manifest. Do not
introduce a second global version catalog. Update coupled declarations,
lockfiles, checksums and affected evidence together through an explicit review.
Checker execution never upgrades, fetches, reformats or rewrites dependencies.
## Sibling paths and moving inputs
- Paths inside the repository use that checkout's source identity. A dependency
outside it, including a symlink into a sibling checkout, needs a documented
development purpose and release qualification boundary. A `version` beside
`path` does not freeze the sibling's bytes.
- Release qualification must select and verify the external repository's exact
commit and clean state, or use a content-addressed immutable artifact. Bind
its lockfile and build inputs to the resulting evidence. Reuse that identity
during retry; do not silently read a newer sibling HEAD.
- A maintainer may approve following a named branch during development, as with
a separately published asset repository. Record the repository, selector,
owner/reason and the command or procedure that freezes and verifies its resolved
identity before qualification. Resolve once for that attempt; retries use the
recorded identity. Such an exception is not permission for floating release
inputs or for a general exemption from pinning.
- Existing local exceptions must be deliberately adopted, not silently replaced.
Merely finding a branch or sibling path in current code does not establish
approval. Maintainer authorization remains governed by the baseline.
## Automated checks and exceptions
Run `bash scripts/ci/check-dependency-pins.sh` in CI and the complete release
gate. Use `--consumer /path/to/repository` for read-only local inspection. The
checker reads tracked and non-ignored new Cargo manifests, workflows and action
definitions; it parses YAML/TOML with Mike Farah `yq`, rather than treating
comments, shell strings or line layout as declarations. `jq` and Git are also
required; Cargo discovers actual workspace roots without downloading dependencies.
The selected Rust toolchain must already be installed; the checker disables
rustup's automatic toolchain installation as well as Cargo network access.
It checks Git/action/check-out/container selectors, Cargo wildcard/exact
requirements, paths outside the repository and the presence of tracked workspace
lockfiles. It covers workspace, development, build, target-specific and patch
dependency tables. It does not resolve registry graphs, prove that a shell command
uses `--locked`, inspect arbitrary download scripts/Dockerfile contents, or verify
live release evidence. Consumer gates enforce those execution boundaries with
locked commands and their existing qualification checks. A passing declaration
check alone is not release qualification.
Document approved exceptions in the local overlay or its linked policy, including
their qualification procedure. Machine-readable entries live in the optional
`ci/dependency-pinning-exceptions.json`, a JSON array with these exact fields:
```json
[
{
"rule": "cargo-exact",
"file": "Cargo.toml",
"subject": "example-runtime",
"value": "=1.2.3",
"reason": "Must match the macro crate's generated API.",
"evidence": "AGENTS.md#dependency-exceptions"
}
]
```
Allowed rule names are `cargo-exact`, `cargo-external-path` and `checkout-ref`.
For Cargo, `subject` is the declared dependency name/alias and `value` is the
literal version requirement or external path. For a checkout, `subject` is
`with.repository` and `value` is its explicit `with.ref`. `file` is the declaring
file, even when a Cargo dependency is inherited elsewhere. Evidence must name a
tracked local document, optionally with an anchor. No wildcards or blanket
suppressions: entries match all four identity fields exactly. Changed selectors
need reviewed reasons; remove exceptions when their maintained need ends. This
file records the approved decision, not a new source of versions or an issue ledger.
The checker validates structure and matching scope; human review establishes approval.
Consumers vendor the checker and `scripts/ci/dependency-pins.jq` together. Prepare
a reviewed `yq` version (4.47.2 or later in v4) before running checks; the shared
`install-yq.sh` accepts explicit version/digest inputs and uses the existing
checksum verifier. Shared Tooling's own selections live in `ci/tool-versions.env`.
Qualify adoption on each declared native host. Do not silently change dependency
versions, toolchains or release inputs while adopting this policy.
References: [Cargo version requirements and Git selections](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html),
[Cargo lockfiles](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html),
[GitHub Action pinning](https://docs.github.com/en/actions/reference/security/secure-use#using-third-party-actions).