Skip to content

No per-PR gate links the perry-ext-* crates, so a perry-runtime change can break them until the next tag #7656

Description

@proggeramlug

A perry-runtime change broke the link of five perry-ext-* crates and no per-PR gate could have caught it (#7650 → fixed in #7655). It would have surfaced at the next tag, days later.

The gap

cargo-test scopes a per-PR run to the changed crates' reverse-dependency closure (scripts/ci_test_scope.py); the full workspace runs on tags and nightly only. perry-ext-* sits outside the closure of a GC change to perry-runtime, so nothing built it.

But the ext crates are structurally more fragile than the closure suggests, not less: they link a feature-stripped runtime through perry-ffi's runtime-link, built with -Wl,-dead_strip. Any new reference edge inside perry-runtime can keep a chain alive that the stripper had been removing, and the failure is a link error, not a test failure:

Undefined symbols for architecture arm64:
  "_js_blob_new", "_js_fetch_with_options", "_js_fetch_notify_signal_aborted"

In #7650 the edge was one added call — pin_objectarena::classify_heap_space — in code that had previously done a raw flag write.

Proposal

Add a cheap per-PR arm that links (not runs) the ext crates whenever crates/perry-runtime/** or crates/perry-stdlib/** changes:

cargo test --release --no-run -p perry-ext-pdf -p perry-ext-lru-cache \
    -p perry-ext-node-forge -p perry-ext-mongodb -p perry-ext-http

Those five are the ones that actually failed; a wider set is better if it is affordable. Measured cost on a dev Mac from a warm target dir: ~5 minutes for all five together, and it needs no runtime behaviour, no fixture and no host pinning — it is a linker check.

Alternatively, teach ci_test_scope.py that perry-runtime/perry-stdlib changes pull in perry-ext-* regardless of the declared dependency graph, since the real coupling here is the link, not the crate graph.

Two things to get right

  1. The gate must assert it linked something. A scope rule that silently selects zero ext crates would be green forever — the failure mode this repo has shipped four times (gc: no reachable configuration exercises an evacuating minor with unpinned runtime locals — the #6655/#6935 bug class is untestable #6942/GC testing: PERRY_GC_FORCE_EVACUATE is inert for gc()-driven tests (full mark-sweep + forced conservative scan) — stress claims may be unsupported #6946, gc-matrix: --pressure disables the very path #7019 added — the 'default' arm runs ZERO copying minors on all 22 corpus rows #7024, gc-matrix: the 'moved' liveness counter sums the C4b mark-sweep evacuation with the copying minor, so a move-arm can be green with zero scavenged objects #7025). Assert a non-zero count of built targets.
  2. Run it once before making it required, per CLAUDE.md's corollary: a new gate has never been green, so promoting it immediately blocks every open PR.

Context: #7650, #7655; the ext staticlib feature-stripping is also behind #7522 and #6847.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions