Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-0017: Supply-chain and dependency policy

Context

The owner’s standing preferences for every repository:

  • No automated version-update bots (no .github/dependabot.yml, no Renovate, no equivalent).
  • GitHub Dependabot security alerts stay enabled as a repository setting (alerts only, no pull requests).
  • Dependencies are updated in batched agent-run sessions, roughly monthly or immediately when a security alert fires: bump everything, run the full verification gate, fix what breaks, land one green pull request that explains the changes.
  • Every dependency version must be at least 24 hours old before it is installed; never update a pinned dependency to a version younger than 24 hours.
  • Keep passive fences wherever they exist: exact-pinned versions, committed lockfiles, ignore-scripts=true, audit and lockfile-lint gates in CI.
  • If a repository documents its dependency posture, update that document in the same pull request as any posture change.

A recent incident shows why the age rule matters: on 2026-08-20 a compromised release of the Rust crate arrayref (0.3.10) was published with a dependency whose build script executed malicious code; it was removed about 86 minutes later, after 2,285 downloads (RUSTSEC-2026-0260). Cargo cannot disable build scripts, so a 24-hour minimum age is the defense that would have kept it out.

Tool capabilities verified on 2026-10-03:

  • pnpm has minimumReleaseAge (minutes; 1440 is the default since pnpm 11) with a strict mode, and pnpm install --frozen-lockfile re-checks locked versions and fails on too-young ones. Since pnpm 10 dependency build scripts do not run by default; pnpm 11 replaced the old allowlist settings with allowBuilds, made strictDepBuilds the default, and blocks exotic sub-dependencies by default. trustPolicy: no-downgrade fails installs when a version has weaker provenance than earlier releases. The current major is pnpm 12.
  • Cargo gains a native setting, [registry] global-min-publish-age, in Rust 1.100 (2026-11-12); earlier stable releases ignore it. Cargo does not re-check versions already in Cargo.lock. The crates.io index carries a publish time (pubtime) per version, and the API exposes created_at.
  • lockfile-lint supports npm and Yarn lockfiles only, not pnpm’s.
  • vcpkg pins exactly with builtin-baseline plus overrides; neither vcpkg nor Conan has a release-age gate. aqtinstall (for installing pinned Qt in CI) verifies SHA-256 checksums from the official Qt download site.
  • GitHub offers a setting to require Actions pinned to full commit SHAs (since 2025-08-15), immutable releases (generally available since 2025-10-28) and artifact attestations. zizmor lints workflows (and flags unpinned or known-vulnerable actions); pinact pins actions and can verify a minimum age.
  • Dependabot alerts can be enabled without version updates or security-update pull requests; it does not alert on SHA-pinned actions, which zizmor’s known-vulnerable-actions audit covers.

Decision

Policy (all repositories)

  1. No update bots, ever. CI fails if .github/dependabot.yml, renovate.json or similar configuration appears (X-003 adds this check).
  2. Dependabot alerts on (including malware alerts); Dependabot security updates off; secret scanning and push protection on.
  3. Batched updates only in the monthly dependency session or a security-alert session, using the prompts in plan/06. One green pull request per repository per session.
  4. 24-hour minimum age for every package, toolchain, CI action and tool version, enforced by tooling wherever possible (below) and stated in the pull request otherwise. If a security fix is younger than 24 hours, mitigate (disable the affected feature, pin an unaffected version) and update once it is eligible.
  5. Exact pins and committed lockfiles. Builds use --locked / --frozen-lockfile.
  6. Install scripts disabled; any exception is named in an allowlist with a justification.
  7. Audit and lockfile-integrity gates run on every pull request and nightly.
  8. License allowlist: MIT, MIT-0, Apache-2.0 (including the LLVM exception), BSD-2-Clause, BSD-3-Clause, ISC, Zlib, 0BSD, BSL-1.0, CC0-1.0, Unicode-3.0, Unicode-DFS-2016, MPL-2.0. Fonts and data packs may also use OFL-1.1, the GUST Font License, CC BY 4.0 and LGPL with font exceptions. Qt is used under LGPL-3.0 (dynamic linking). Anything else requires an ADR amendment. Dual-licensed packages qualify if one option is on the list. Copyleft dependencies (GPL, AGPL, and LGPL other than Qt) stay excluded even though our own code is GPL or AGPL: they could not be used in the Apache-2.0 areas and would block the proposed app-store permission (ADR-0003).
  9. Minimal dependencies. Every new dependency is justified in its pull request (purpose, alternatives, license, maintenance, publish date, transitive count).
  10. Posture documents (this ADR and each repository’s AGENTS.md dependency section) are updated in the same pull request as any posture change.

Mechanisms per ecosystem

EcosystemPins and lockfile24-hour ageScriptsAudit / integrity
Rust (core, server)Direct dependencies with exact =x.y.z requirements in [workspace.dependencies]; Cargo.lock committed; --locked everywhere.cargo/config.toml: [registry] global-min-publish-age = "1 day" (enforced natively from Rust 1.100; until the workspace moves to 1.100, update sessions run resolution with a toolchain that enforces it); plus a CI script that, for every package version added or changed in a pull request’s Cargo.lock, checks the crates.io publish time is at least 24 hours before the commit that introduced itBuild scripts cannot be disabled in Cargo: minimize crates with build.rs, avoid -sys crates that compile C, review build scripts of new dependenciescargo deny check (RustSec advisories, licenses, bans, sources limited to crates.io and pinned Git tags); evaluate cargo vet with imported audits in Phase 2
pnpm (web)packageManager pins pnpm; save-exact=true; pnpm-lock.yaml committed; --frozen-lockfileminimumReleaseAge: 1440 with strict mode; frozen installs re-check locked versionsstrictDepBuilds: true, empty allowBuilds unless justified; ignore-scripts=true in .npmrc for any npm usepnpm audit; trustPolicy: no-downgrade; blockExoticSubdeps; a lockfile-integrity script (every package resolved from the npm registry with a sha512 integrity hash; no tarball or Git sources) in place of lockfile-lint
C++ (desktop)Qt only; version pinned in CI and packaging; installed with aqtinstall (checksums verified) at an exact version; any future vcpkg dependency pinned with builtin-baseline plus overridesQt release date and any baseline commit date checked by a script to be at least 24 hours oldNot applicableQt security advisories tracked in the monthly session
Python tools in CI (e.g. aqtinstall, REUSE)Exact versions with --require-hashesPublish dates checked in the update sessionNot applicablepip-audit on the tool requirements
GitHub ActionsEvery uses: pinned to a full commit SHA (repository setting enforced); version in a trailing commentpinact run --check --verify-min-age --min-age 1Not applicablezizmor on every workflow change; minimal permissions:; no pull_request_target with untrusted checkout
Toolchains (Rust, Node.js, pnpm, CMake, Qt)Pinned exactly (rust-toolchain.toml, .nvmrc/engines, packageManager, CI)Release date checked in the update session——

Publishing (from Phase 1)

crates.io and npm trusted publishing from CI (no long-lived tokens), provenance and artifact attestations on every release, immutable releases enabled.

Consequences

  • New versions reach BayanDocs no sooner than a day after release, which blocks most “malicious version published and pulled within hours” attacks.
  • Updates arrive in reviewable batches rather than a stream of bot pull requests.
  • Some scripting (Cargo lockfile age check, pnpm lockfile integrity check, Qt date check) is ours to maintain; X-003 builds it once and shares it.

Alternatives considered

  • Dependabot or Renovate version updates with delays: rejected by owner policy.
  • Vendoring all dependencies: strong control, but heavy repositories and awkward updates; reconsider only for the core if registry trust degrades.
  • cargo-vet from day one: valuable but adds friction before the dependency set stabilizes; evaluate in Phase 2.

Amendment 2026-10-04: tools in agent sessions

Agent sessions run in a Claude Code cloud environment whose setup script, scripts/cloud-environment-setup.sh, installs the project’s tools. It is part of the dependency posture and follows this policy:

  • Pins and age: every tool, toolchain and package is pinned to an exact version that was at least 24 hours old when pinned; Ubuntu packages come from the signed archive frozen at a snapshot date (apt-get --snapshot).
  • Verification: release archives are checked against SHA-256 hashes written in the script; Python tools are installed with pip --require-hashes from wheels, or, where no wheel fits (REUSE 6.2), from a hash-pinned source archive built with a hash-pinned build backend and no build isolation; Node.js against its SHA-256; pnpm through Corepack with a SHA-512 pin; Qt by aqtinstall against hashes from download.qt.io; Rust toolchains by rustup. If a release download is refused, the tool is built from source instead (Cargo with --locked, Go with its checksum database).
  • No silent upgrades: rustup self-update is disabled, and Corepack must not resolve “latest” (COREPACK_DEFAULT_TO_LATEST=0). On 2026-10-04 Corepack’s default resolved pnpm 12.9.1, published about eight hours earlier, which is why the pin is explicit. npm’s user configuration sets ignore-scripts=true. Once the pinned Rust toolchain is 1.100 or later, the script also sets Cargo’s global-min-publish-age globally (earlier versions only warn about it).
  • Updates: only in the monthly dependency session, together with the repositories’ own pins; Python hash lists are regenerated with scripts/python-tool-hashes.py. The owner then pastes the new script into the environment settings (plan/06).

Amendment 2026-10-04: installing Qt for bayan-desktop

Decided by the owner on 2026-10-04 during DESK-001 (BayanDocs/bayan-desktop pull request 1), choosing option A below.

Context. aqtinstall 3.3.0 (published 2025-06-02), its newest release, cannot install Qt 6.11 or later on Windows: Qt’s repository now has a separate sub-repository for each Windows compiler, which only aqtinstall’s development branch supports. Qt’s official online installer is no alternative, because it needs a Qt account login that CI would have to store as a secret.

Options considered. (A) Write a small Qt installer of our own that uses only the Python standard library, makes the same hash checks against download.qt.io as aqtinstall, and removes aqtinstall’s 25 third-party Python packages from CI and developer machines; it needs this amendment. (B) Pin aqtinstall to an exact, hash-checked commit of its development branch that contains the fix: the smallest change, but it runs code that was never released. (C) Wait for aqtinstall’s next release, leaving the Windows build unverified for an unknown time. The owner chose A.

Decision. bayan-desktop installs Qt with its own installer, scripts/install-qt.py, which uses only the Python standard library. In the mechanisms table above, “installed with aqtinstall” in the C++ (desktop) row now reads “installed with bayan-desktop’s scripts/install-qt.py”; the rest of that row is unchanged.

  • Pins: deps/qt.json records the exact Qt version, its release date and, for each platform, the repository path, the package and the archives to install. The 24-hour rule and the release-date check that X-003 adds apply as before.
  • Verification, with the same trust model as aqtinstall: the repository index and every archive are checked over HTTPS against the SHA-256 hashes that Qt publishes on download.qt.io; archives are downloaded from Qt’s master server (master.qt.io), never from third-party mirrors, and nothing is extracted unless its hash matches. HTTP redirects are refused, so every file comes from the server named in its URL.
  • Extraction: every archive entry is checked before anything is extracted: no absolute paths, no “..”, only plain files, directories and symbolic links that stay inside the installation, and no paths through links. Paths are compared the way case-insensitive file systems compare them (case folding and Unicode normalization), so a different spelling cannot slip past the checks on macOS or Windows, and two entries whose names differ only in case are refused unless both are directories. Links are carried from one archive to the next, so an entry of a later archive cannot pass through a link created by an earlier one, and the directory an archive is extracted into must not be, or pass through, a link. The real path of every link is checked again after each archive, and the installation is moved into place only when it is complete. Archives are extracted with the pinned CMake (cmake -E tar).
  • Tests: the installer has its own tests, which run against a fake repository on disk and served over HTTP on the local machine, as part of bayan-desktop’s verification gate.
  • Agent environment: scripts/cloud-environment-setup.sh keeps installing its Linux Qt with aqtinstall (which supports Linux) until a monthly dependency session decides whether to switch it to this installer. When bayan-desktop is attached to a session, the setup script also runs that repository’s scripts/dev-setup.sh, which installs its hash-pinned CMake, Ninja, clang-format and clang-tidy and reuses the preinstalled Qt. These tools are not put on the default PATH: the pinned CMake is version 4, which refuses projects that declare cmake_minimum_required below 3.5, as some older C and C++ projects still do (for example ones that Rust build scripts compile), so it must not replace the cmake that other repositories use. Agents working on bayan-desktop load ~/.local/share/bayandocs/desktop-tools/env.sh first, as its AGENTS.md says.

Consequences. CI and developer machines no longer need aqtinstall’s 25 third-party Python packages (some compiled, some LGPL-licensed) to install Qt. In exchange, the project maintains about 400 lines of Python (comments included) and must adapt them when Qt changes its repository layout again; the installer fails with a clear message, and installs nothing, when the index does not match the pins. deps/qt.json gains fields that change with every Qt version, so updating Qt in the monthly session means updating all of them and running bayan-desktop’s verification gate.

Amendment 2026-10-04: what the license allowlist covers

Decided by the owner on 2026-10-04, answering open question 3 of DESK-001 (BayanDocs/bayan-desktop pull request 1).

Context. Decision 8 lists the licenses that BayanDocs’ dependencies may use, but it does not say which software the list applies to, and DESK-001 met two cases it does not settle. First, some tools that build and check BayanDocs without ever becoming part of it use other open-source licenses: the Python interpreter that runs CI’s build tools is under PSF-2.0, and ShellCheck, which the agent environment installs, is under GPL-3.0-or-later. Second, decision 8 accepts “Qt under LGPL-3.0”, but Qt’s libraries contain third-party code under its own licenses. The SBOM files (software bills of materials, machine-readable lists of components) shipped with Qt 6.12.0 list about a hundred third-party entries for qtbase alone: code bundled into Qt’s libraries, such as HarfBuzz, libpng, libjpeg-turbo, PCRE2 and FreeType’s rasterizer, mostly under MIT and BSD licenses, and references to system libraries that Qt uses on some platforms.

Decision. Decision 8 is read as follows:

  1. The allowlist applies to everything that is linked into, or shipped with, BayanDocs software: the desktop and web applications, the server, and the packages, installers and containers that distribute them. It includes every package dependency of our code, whether it is used at run time or only at build time (for example Cargo build dependencies, build scripts and procedural macros, or npm packages that a bundler processes), because their code or output can end up in what we ship.
  2. Standalone build, test and CI tools that are installed separately, are never a package dependency of our code and are never shipped (for example compilers, interpreters, build systems, linters, formatters and test runners) may use any OSI-approved open-source license, such as PSF-2.0 for Python or GPL-3.0-or-later for ShellCheck. Every other rule of this ADR still applies to them: exact pins, the 24-hour minimum age, hash verification wherever the ecosystem allows it, and a justification in the pull request that adds or changes them.
  3. Third-party code that Qt bundles in its libraries, as listed in the SBOM files of the pinned Qt release (the sbom/ folder of the Qt installation), and the ICU libraries that Qt’s repository supplies with it on Linux, are accepted as part of the Qt decision (ADR-0013), under the licenses those files record. Where a component offers a choice of licenses, BayanDocs uses the permissive one (for example the FreeType License rather than GPL-2.0 for FreeType’s rasterizer). The packaging work package DESK-104 ships the notices these licenses require, such as the FreeType and Independent JPEG Group credits. When the monthly dependency session upgrades Qt, it compares the new SBOM files with the old ones and asks the owner before accepting a newly bundled component whose licenses offer no permissive or LGPL option.

Consequences. CI’s Python interpreter and the agent environment’s tools need no further exception. Nothing changes for package dependencies, including build-time ones: they stay under the allowlist as before. DESK-104 gains a deliverable, the third-party notices for Qt’s bundled code and ICU, taken from the SBOM files of the Qt release it packages, and each Qt upgrade gains a short SBOM comparison.

Amendment 2026-10-04: pnpm without Corepack

Node.js 25 and later no longer ship Corepack (the Node.js 26.10.0 download contains only node, npm and npx), and Node.js 26 is expected to become the Active LTS release in late October 2026, so the agent environment’s Corepack step would break at the next Node.js update. Corepack was also weaker than it looked: it checked only pnpm’s small JavaScript wrapper against the packageManager hash, while the 60 MB native binary that does the work was downloaded on first use and checked only against the npm registry’s signature. Corepack is therefore no longer used anywhere:

  • Agent environment: scripts/cloud-environment-setup.sh installs pnpm’s native binary, the npm package @pnpm/exe.linux-x64 at the pinned PNPM_VERSION, only after checking it against a SHA-512 hash written in the script (in hex, like the other pins), and puts it on PATH ahead of Node.js. The COREPACK_* settings are gone.
  • Inside each repository, pnpm switches itself to the version pinned in that repository’s packageManager field, verified against the repository’s lockfile and npm’s signature.
  • bayan-web pins pnpm’s native binary through its lockfile: the first YAML document of pnpm-lock.yaml records the sha512 of @pnpm/exe.<platform> for each supported platform, and its scripts/dev-setup.sh installs that binary only after checking the download against it. Its policy check also fails when the pnpm running the gate (reported in npm_config_user_agent) is not the pinned version.
  • Updates: the pnpm pin in the setup script changes only in the monthly dependency session, like every other pin.

This amendment replaces only the Corepack parts of the amendment “tools in agent sessions” above (“pnpm through Corepack with a SHA-512 pin” and COREPACK_DEFAULT_TO_LATEST=0); everything else in that amendment and in the other amendments still applies.

Amendment 2026-10-04: root certificate data for the server’s TLS

Decided by the owner on 2026-10-04 during SRV-001 (BayanDocs/bayan-server pull request 1), as part of ADR-0028.

Context. ADR-0028 chooses rustls with the aws-lc-rs provider for the server’s TLS. The server’s PostgreSQL driver, sqlx 0.9, offers that combination only together with the webpki-roots crate: Mozilla’s list of trusted root certificate authorities, compiled into the program as data. webpki-roots (1.0.9, published 2026-07-18) is licensed CDLA-Permissive-2.0, a permissive license for data published by the Linux Foundation, which is not on the allowlist; because the crate is linked into the server, the amendment “what the license allowlist covers” applies to it.

Decision. CDLA-Permissive-2.0 is accepted for the webpki-roots crate only, recorded as a per-crate license exception in bayan-server’s deny.toml, not as a new entry in the general allowlist. Wherever a library can use the operating system’s certificate store (the container’s CA bundle) instead, the server uses that, as ADR-0028 requires; this exception exists because sqlx offers no such option with aws-lc-rs. ADR-0028 also accepts aws-lc-sys, a -sys crate whose build script compiles AWS-LC’s C code; like libsqlite3-sys, it is a documented exception to “avoid -sys crates that compile C” in the Rust row of the mechanisms table.

Amendment 2026-10-08: the libFuzzer license for fuzzing

Decided by the owner on 2026-10-08 during CORE-004 (BayanDocs/bayan-core pull request 13), answering question 3 of its report.

Context. Every parser gets a fuzz target (ADR-0025, decision 4), and bayan-core’s fuzz/ folder, a separate Cargo workspace for them, plans to use cargo-fuzz (CORE-005, CORE-006). cargo-fuzz’s targets depend on the crate libfuzzer-sys, which bundles LLVM’s libFuzzer and is licensed (MIT OR Apache-2.0) AND NCSA. NCSA (the University of Illinois/NCSA Open Source License, a permissive license) is not on the allowlist of decision 8, and the amendment “what the license allowlist covers” applies the allowlist to every package dependency of our code, so CORE-004 fuzzed with a dependency-free fuzzer of its own instead.

Decision. NCSA is accepted only for fuzzing dependencies that never ship: the dependencies of bayan-core’s separate fuzz/ workspace and its fuzz targets, such as libfuzzer-sys, which are never linked into, or shipped with, BayanDocs software. The license allowlist otherwise stays as it is: nothing that ships, and no dependency of the main workspaces, may use NCSA. Every other rule of this ADR applies to the fuzzing dependencies too: exact pins, a committed lockfile, the 24-hour minimum age and a justification in the pull request that adds them.

Consequences. CORE-005 and CORE-006 can add their fuzz targets with cargo-fuzz, as fuzz/README.md in bayan-core assumes; the work package that adds the first target records the NCSA exception in the fuzz/ workspace’s own license check, never in the main workspace’s deny.toml. Continuous fuzzing in CI becomes possible, and is a follow-up.

Amendment 2026-10-10: CodeQL for code scanning in CI

Decided by the owner on 2026-10-10 in the review of work package X-002 (BayanDocs/docs pull request 30 and the four pull requests that go with it), choosing option A below.

Context. X-002 adds code scanning with CodeQL, GitHub’s static analysis, to all five repositories: the work package asks for it by name, and OpenSSF Scorecard’s SAST check looks for it. CodeQL runs only in CI. Its engine, the CodeQL bundle (the CodeQL command-line program with its extractors and queries), may be used at no cost to analyse open-source code, but under the GitHub CodeQL Terms and Conditions, which are not an open-source license. The amendment “what the license allowlist covers” lets standalone CI tools use any OSI-approved open-source license and says nothing about other licenses, so the bundle needs a decision of its own. The action that runs it, github/codeql-action, is MIT-licensed and needs no exception.

Options considered. (A) Accept the CodeQL bundle as a narrow exception for code scanning in CI, with every other rule of this ADR still applying to it. (B) Leave CodeQL out of X-002: no static analysis of the project’s C++, Rust, TypeScript, JavaScript, Python and workflows, and Scorecard’s SAST check stays at 0. The owner chose A.

Decision. The CodeQL bundle, as GitHub publishes it in the codeql-bundle-v<version> releases of github/codeql-action, is accepted under the GitHub CodeQL Terms and Conditions only for code scanning of BayanDocs’ own repositories in CI, run through github/codeql-action. It is never linked into, shipped with or needed to build BayanDocs software, and neither releases nor developer machines need it. Every other rule of this ADR applies to it: an exact version (CODEQL_VERSION in each repository’s .github/workflows/codeql.yml), whose release must be at least 24 hours old; its SHA-256, taken from the release’s checksum file, written in the workflow and checked before the bundle runs; the same age rule for the Rust toolchain that its Rust extractor installs; and a justification in the pull request that adds or changes it. This amendment accepts no other tool that is not open source.

Consequences. The CodeQL workflows of X-002 may stay, code scanning shows their results, and Scorecard’s SAST check can pass. The monthly dependency session updates the bundle together with github/codeql-action, as the dependency update runbook describes. If GitHub’s terms change so that the bundle can no longer be used this way, the CodeQL workflows are removed, and a replacement needs the owner’s decision.

Amendment 2026-10-12: the bayan-core commit that a shell builds from

Decided by the owner on 2026-10-12 during DESK-002 (BayanDocs/bayan-desktop pull request 6), choosing option B below.

Context. During Phase 0, before bayan-core publishes its first release, shells may build the engine from a pinned bayan-core commit in CI (ADR-0002, decision 4). DESK-002 does so for bayan-desktop: its deps/bayan-core.json pins a commit on bayan-core’s main, and its scripts/build-engine-sdk.sh fetches exactly that commit, checks that it is on main, and builds the engine’s C SDK from it with bayan-core’s committed Cargo.lock. Rule 4 asks for a 24-hour minimum age for “every package, toolchain, CI action and tool version”, and the dependency update runbook calls a dependency anything the project downloads instead of writing itself; neither says whether BayanDocs’ own code, fetched from another of its repositories, counts. The commit that DESK-002 pinned was 10 hours 41 minutes old when it was pinned.

Options considered. (A) Apply the minimum age to first-party pins too: a check like bayan-desktop’s scripts/check-qt-release-age.py makes sure that the pinned commit is at least 24 hours older than the commit that pins it, and than now. A work package that needs a newer engine then waits up to a day, and a bad or hijacked bayan-core commit gets the same day to be noticed as a third-party release. (B) Exempt commits on bayan-core’s main from the minimum age, because they are first-party code that has passed bayan-core’s own gate. The owner chose B.

Decision. A commit on bayan-core’s main that a shell pins in order to build the engine from source during Phase 0 (ADR-0002, decision 4; today deps/bayan-core.json in bayan-desktop) is exempt from rule 4’s 24-hour minimum age. The rule guards against third-party releases that are published and withdrawn within hours, like the arrayref release in this ADR’s context; such a commit is BayanDocs’ own code, which reaches main only through a pull request whose required checks, bayan-core’s verification gate on Linux, Windows and macOS among them, have passed (the main ruleset of scripts/github-settings.py). Everything else still applies:

  • The pin is a full 40-character commit hash on bayan-core’s main, and the shell’s build script fetches exactly that commit and fails unless it is on main (in bayan-desktop, scripts/build-engine-sdk.sh does both).
  • What the commit brings in is not exempt. Every crate in its Cargo.lock stays subject to the 24-hour rule through bayan-core’s own lockfile-age check (cargo xtask check-lockfile-age) and is built with --locked, and the Rust toolchain that the commit pins in rust-toolchain.toml stays subject to it too.
  • Every other rule of this ADR applies to the pin and to the build.
  • Shipped builds use released SDKs and packages pinned by version and SHA-256 (ADR-0002, decision 4), never a commit.

The exemption covers nothing else: not other first-party pins, and not bayan-server’s Git dependencies on core crates (ADR-0002, decision 4). Each would need a decision of its own.

Consequences. A shell can move its bayan-core pin to a new commit on main as soon as a work package needs it, without waiting a day. bayan-core’s pull requests and its gate, rather than the age rule, are the defense against a bad commit, while the age rule keeps covering the third-party crates and the toolchain that the commit brings in. bayan-desktop’s AGENTS.md and the dependency update runbook cite this amendment where they describe the pin. The exemption lapses with Phase 0, when shells stop building from commits and pin released SDKs and packages instead.

Revisit when

Ecosystem tools change (for example Cargo re-checking lockfile ages natively, or lockfile-lint supporting pnpm), a supply-chain incident affects us, or the owner changes the policy.