Skip to content

A promised row can name a type api does not re-export, and nothing checks it #531

Description

@blooop

The gap

public-api.api.txt records the signatures of the promised tier. Nothing checks that a type named in one of those signatures is reachable from api.

So a change can widen a promised function with a parameter type it never re-exports, the snapshot records the new signature happily, public-api goes green, and the promised surface is left naming a type a consumer of api cannot spell.

It has happened twice

Two instances, one caught by a reviewer and one by an unrelated PR's test. Nothing in CI saw either.

Why the existing guards do not cover it

public-api.api.txt is a record of what the surface is, regenerated and diffed by the public-api job. It is doing its job correctly: the signature really did change, and the snapshot really does say so. The question it never asks is whether the names inside a signature are reachable by the same route as the function.

The fold's own test in #520 (api_removal_is_self_sufficient.rs) catches it for one function because it is written to use api paths exclusively. That is a guard for one call site, held in place by a convention that is not stated anywhere and that the next promoted function will not inherit.

Proposed shape

A test over public-api.api.txt itself: for every devlaunch_core::api:: row, extract the fully qualified type paths it names, and assert each one either

  • is itself a devlaunch_core::api:: row, or
  • appears in the file as re-exported from api,

with the primitives and core::/alloc::/std:: paths excluded. Failure names the row and the unreachable type.

It reads a file that is already generated, so it is not a second hand-maintained copy of anything, and it costs nothing at runtime.

Notes for whoever builds it

  • The red test writes itself: revert Fold the removal guard into one workspace_remove and swap the api row #520's api re-export of KeptCopies and the guard should name api::workspace_remove and KeptCopies. Verify it red that way rather than on a synthetic row.
  • Decide deliberately whether a type reachable via a public module path that api does not re-export counts as satisfied. KeptCopies was reachable at devlaunch_core::flows::kept_copies::KeptCopies the whole time; what it was not, was reachable through api. If the promise is "api is self-sufficient", public-elsewhere is not enough, and the test should say so in its name.
  • Related to the standing rule in AGENTS.md about second copies: this is the inverse case, where the generated record is correct and the reachability it implies is not checked at all.

Related: #517, #527, #529 — signals whose green means less than a reader assumes.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions