Skip to content

The api promise file misses Launch::new and Launch::run #352

Closed

Description

@blooop

Question

public-api.api.txt is meant to be the frozen wf promise, where any diff is a breaking change by definition. As built by #338 it is not: renaming api::Launch::run leaves it byte-identical.

cargo public-api renders methods and impls only at a type's canonical path — devlaunch_core::flows::launch::Launch::run, never devlaunch_core::api::Launch::run — so the classifier's devlaunch_core::api\b match cannot see them. The review of PR #347 measured it precisely: the generator emits the api section as 79 contiguous rows and the filter keeps 37. The 42 that fall through into the "regenerate freely" tripwire file include:

  • Launch::new and Launch::run — the only constructor and method of the central promised type
  • CommandContext::new
  • DevcontainerPath::as_str
  • every derived Clone / Debug / PartialEq impl on the four promised types

Widen the classifier so a promised type's canonical-path rows are classified as promise, not as tripwire. The obvious shape is to resolve each api re-export to the canonical path it names and claim that type's rows — but check whether cargo public-api offers this directly before building it by hand, and make sure the partition stays a partition (every row in exactly one file, which rust/devlaunch-core/tests/public_api_snapshots.rs already asserts).

Red-first, and the red is the finding itself: rename Launch::run, and public-api.api.txt must change. A test that mutates nothing proves nothing here — the existing suite passed while this hole was open.

Note the docs were corrected in #338 to stop asserting the converse, so this ticket closes the gap rather than a contradiction.

Activity

  1. blooop commented on Aug 29, 2026

    @blooop
    OwnerAuthor

    Claimed 2026-08-29: queued in the session working map #299's build tickets (waves behind #340/#349's PRs so the api surface and shared JSON writer move once). Build to follow from an opus agent in an isolated worktree. If you are another session seeing this, do not double-build.

  2. self-assigned this
    on Aug 29, 2026
  3. blooop commented on Aug 29, 2026

    @blooop
    OwnerAuthor

    breadcrumb: building this now, stacked on #340's PR (branch wayfinder/devlaunch-340) so the surface moves once rather than twice.

    Restating the contract: the promise file must carry the promised types' methods and impls, so that renaming api::Launch::run diffs it. Verified live on the stacked base first: the regenerated public-api.api.txt is 126 rows and not one of them is a ::new( or ::run(.

    Seams I intend to test at, both of which the ticket names:

    • The classifier, which today is one grep inside the regeneration script and is reachable only by running the whole nightly toolchain. I am giving it a way in that needs no toolchain, so the predicate itself can be exercised on crafted rows: the interesting cases are a canonical-path method row (must be claimed) against a row that merely mentions a promised type in an argument position (must not be). That second one is the real hazard, because a promised type appears in half the signatures in the crate.
    • The checked-in snapshots, where the existing Rust guard already holds the partition. Extended to assert what the ticket says is missing: Launch::new and Launch::run present in the promise file, absent from the tripwire.

    One thing found while measuring that changes the shape of the fix, and which the ticket does not mention: the generator emits each promised type's impl rows twice, once in the api section and again in its own module's section, byte-identical. A path match cannot tell the two copies apart, so claiming the canonical rows moves both. That is the right answer rather than a compromise, since the alternative leaves an identical row in both files and the partition guard is what would catch it, but it means the tripwire file loses those rows entirely.

  4. blooop commented on Aug 29, 2026

    @blooop
    OwnerAuthor

    breadcrumb: built, PR #518. Stacked on #514 (base wayfinder/devlaunch-340), so it retargets to main when that merges.

    The classifier reads the api module's own re-exports, resolves each to the canonical path it names, and claims that item's rows as well as the api-path ones. 395 rows moved from the tripwire file to the promise file and none appeared from nowhere: the two files' totals are conserved exactly, and both were regenerated with the real tool after first reproducing the base's three files byte-identically.

    The ticket's red was met on its own terms. Launch::run renamed and the snapshots regenerated: the promise file now diffs, where before the same rename left it byte-identical.

    Two things worth carrying out of this that the ticket did not anticipate:

    • The generator emits each promised type's impl rows twice, byte-identical, once under the api section and once under the module that owns them. A path match cannot tell the copies apart, so claiming the canonical rows moves both and 143 of the promise file's 521 rows are duplicates. Deduplicating would break the partition, since the two files are complements by construction.
    • The limit moved rather than closed. A type api never re-exports but a promised signature hands back is still binary surface: Launch::run returns flows::launch::Launched, so renaming one of that type's fields breaks a consumer and diffs the tripwire file alone. That is now what the three places describing the promise file say, in place of the old caveat about methods.

    One guard had to go rather than be extended: "every promised row is a devlaunch_core::api declaration" stopped being true and could not be made true without a second copy of the classifier living in Rust. It is replaced by a fixed point, feed the classifier both files and it must hand back the same two, which covers what that one covered and also catches a pair regenerated by different rules. The classifier is reachable without a nightly toolchain now, as a filter over rows on stdin, which is what lets both the Rust and Python guards exercise the real rule instead of restating it.

  5. blooop commented on Aug 29, 2026

    @blooop
    OwnerAuthor

    wf-review verdict: Comment (nothing blocking) on #518 — reviewed against merge base 9925291; two material findings (residual caveat understates scale 1 vs 38 types; the doc guard passes with that caveat deleted).

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions