Skip to content

Lock inventory and the vendored PyPI router disagree on which lock governs when poetry.lock or pdm.lock has no packages #1114

Description

[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: discussion #560 register.

Kind: bug. Source: new finding (follow-up to #1044 / E75); register E91.

Problem

#1044 moved the PyPI tool-lock order into one table, formats::governing_locks. The lock inventory still keeps its own copy of that order, and the two copies use different rules:

So with a package-less poetry.lock (Poetry writes package = [] for a project that declares no dependencies, for example one that uses Poetry only for packaging) beside a requirements.txt that pins the dependencies:

scan --mode vendored therefore offers a package that it then refuses to vendor, and the remedy it prints (poetry lock) does not help: re-locking an empty Poetry project produces the same empty lock. The same split applies to a package-less pdm.lock.

Proof by execution. I ran a throwaway unit test in vendor/pypi.rs twice on ea09714. The project had poetry.lock = package = [] plus a [metadata] section, a pyproject.toml with [tool.poetry], and requirements.txt = six==1.16.0:

INVENTORY: ["pkg:pypi/six@1.16.0"]
ROUTER: Ok(("Poetry", [VendorWarning { code: "pypi_multiple_lockfiles", detail: "multiple python lockfiles found; wiring `poetry.lock` — installs driven by requirements.txt will still install the UNPATCHED registry bytes" }]))

The output was identical on both runs.

Symptoms

None filed. This is the "lock inventory's own PyPI order" that #1044 left outside the table.

Impact

Proposed change

  • Add one predicate to formats::governing_locks that both callers use, for example pypi_governing(view) -> Option<&'static str>. It applies one rule for a tool lock that resolves no packages. The recommended rule is the inventory's: a package-less tool lock does not govern, so requirements.txt (or the next tool lock) does.
  • inventory_pypi_locks_raw_in asks that predicate instead of its hand-coded if !uv_lock { poetry else pdm else … } chain. Delete the chain.
  • detect_pypi_flavor asks the same predicate, so this project routes to Requirements, and the pypi_multiple_lockfiles warning no longer names poetry.lock as the governor.
  • Keep the inventory's documented Pipfile.lock + requirements.txt union and the uv parse-success rule (an unparseable uv.lock falls through), stated once in the table's docs.

Size and scope

Acceptance criteria

  • A regression test: the project above routes to PypiFlavor::Requirements, and inventory_project and detect_pypi_flavor agree on the governing source. Add the same for a package-less pdm.lock.
  • depless_poetry_lock_falls_through_to_requirements and the formats::governing_locks tests stay green.
  • The vendored PyPI e2e suites (e2e_vendor_pypi*, mode_migration_pypi) stay green.
  • No other file in vendor/lock_inventory/ spells the PyPI tool-lock order.

Dependencies


Backlog review — 2026-10-08

Priority: P1 → P3. An empty tool lock beside requirements causes a false refusal, not a false security attestation. Narrow layout edge; retain the inventory/router fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpm:poetryPoetrypriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions