Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions quest/m1/ffi-refusals/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ and listed in the census. moq-mux's `codec::annexb::build_prefix` exports once m
re-exporting `bytes` whole, as [moq-pin](/quest/m1/ffi-refusals/moq-pin.md) has it,
since the re-export makes `Bytes` a handle.

## Waiting

- Maintainer: how a future or a kept closure crosses from C. Options: (a) the exporter gains
a foreign future, a poll function, its data, and `free`, the inverse of the futures it
returns, and a kept closure, a function, its data, and `free`, each wrapped in every
language's native future and function, so moq changes nothing; (b) moq's signatures
change, such as the stand-in re-exporting only kio's `Instant` from `clock`, and kio's and
moq-tokio's runtime helpers leaving what the facade reaches. Recommendation: (a), since
each has a C shape and AGENTS.md counts a source change a consumer needs as a gap; `repair`
then takes a foreign `Read + Seek`, while `panic::message` and `tls::Serve::resolver`, which
name what C can never hold, go to moq to wrap or drop, as the epic decided for third-party
types.
Decided 2026-11-11 (maintainer, /quest-unblock): ✅ (a) the exporter gains a foreign future (a
poll function, its data, and `free`, the inverse of the futures it returns) and a kept closure
(a function, its data, and `free`), each wrapped in every language's native future and
function, so moq changes nothing (rejected: (b) moq's signatures change). `repair` then takes
a foreign `Read + Seek`; `panic::message` and `tls::Serve::resolver` go to moq to wrap or
drop. The maintainer dislikes callbacks and closures in an API, so a moq quest for a
closure-free API is planned separately only if research finds a simpler one; it does not gate
this quest.

Found on 2026-11-11 at moq's `main` `d528b675`, four of the eleven leave without (a):

- moq-tokio's `server::Request::within` leaves through a planned moq quest that replaces it
with `Request::poll_expired` and `expired()`.
- `kio::wait` stops being reachable once the [pin bump](/quest/m1/ffi-refusals/moq-pin.md)
drops `pub use kio;`.
- moq-tokio's `panic::isolate` leaves when the stand-in in `ffi/justfile` stops re-exporting
`moq_tokio::panic`, from which moq-ffi ships nothing; this quest makes that change, and
`panic::message` leaves with it.

kio's other five, `Waiter::poll_future`, `clock::timeout`, `clock::timeout_at`,
`clock::drive`, and `Waiter::batch`, still need (a), as `repair` does.
27 changes: 16 additions & 11 deletions quest/m1/ffi-refusals/moq-pin.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
The moq pin moves to moq's current `main`, whose crates re-export no whole crate, moq's own
included. `just ffi census` there lists no whole-crate re-export refusal. Its "type not
reachable from the crate root" refusals are `kio::Pending` and `lite::version::Version`,
which Waiting covers, and moq-quic's stats and varint types under moq-tokio's `quic::driver`,
which the exporter change below exports, and moq-quic's stats and varint types under moq-tokio's `quic::driver`,
which [moq-dev/rust2#683](https://github.com/moq-dev/rust2/pull/683) drops. At `ffcb7cd9` there were 20 such refusals (18 Guard and Side, and
`Version`), and 9 at moq's head on 2026-11-09, measured in
[#676](https://github.com/moq-dev/rust2/pull/676).
Expand Down Expand Up @@ -55,19 +55,24 @@ Decided with the maintainer on 2026-11-09: bump to moq's current main now, and n
Goal to the two moq-owned types. Rejected: wait for moq to fix the rest first. Not landed:
moq's `main` fails `just ts moq` and `just ffi moq-cpp`, so the bump waits on Waiting.

Decided 2026-11-11 (maintainer, /quest-unblock): ✅ (a) rust2 exports a type the crate root does
not reach by its defining path, such as `kio::Pending` and `lite::version::Version`; a type
whose defining path is itself private stays refused (rejected: (b) moq makes both reachable).
This quest makes the exporter change, in `ffi/src/collect.rs`, before the bump.

Measured on 2026-11-11 at moq's `main` `d528b675`: the bump also has to fix 37 stale
`ffi/moq/ffi.toml` entries, such as excludes naming `bytes` and `kio`, which no longer
exist, and the new refusals the census lists there.

## Waiting

- moq-dev/moq: `rind` builds for wasm32. moq-net depends on `rind`, whose md5 inline asm has
no wasm32 arm, so `just ts moq <checkout>` fails at moq's `main`. Done when it passes there.
Still failing on 2026-11-11 at moq `d528b675`: `rind` 0.3.1, and 0.3.2 too, has an
unconditional `asm!` in `src/low/generic/md5.rs:22`, so the wasm32 build fails with E0658.
The fix belongs in `rind` itself, then a bump in moq.
- moq-dev/moq: moq-tokio builds without tripping `recursion_depth_exceeding_limit` (an
overflow proving `Send` on `Instrumented<...>`), which `just ffi moq-cpp <checkout>` denies.
Done when it passes at moq's `main`.
- Maintainer: decide how `kio::Pending`, named by `dial_tcp` and `dial_track`, and
`lite::version::Version` stop refusing as "type not reachable from the crate root", which
`just ffi census <checkout>` lists at moq's `main`. Options: (a) rust2 exports a type the
crate root does not reach by its defining path, recommended, since it asks moq for no source
change, as the maintainer's 2026-11-09 decision does; a type whose defining path is itself
private stays refused. (b) moq-dev/moq makes both reachable from moq-net's root, by name or
by wrapping; no moq quest owns this yet. Whichever is chosen, its change lands before the
bump: the exporter change under (a), or moq's fix under (b). Done when the census at moq's
`main` lists neither type.
Done when it passes at moq's `main`. Still failing on 2026-11-11 at moq `d528b675`, at
`rs/moq-tokio/src/client.rs:722`, on the `Instrumented<...>` from
`tokio::spawn(panic::isolate(time::run(driver)).instrument(..))`.
17 changes: 7 additions & 10 deletions quest/m1/ffi-refusals/small-shapes.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [XS] Arrays export
# [S] Arrays export

## Goal

Expand All @@ -16,12 +16,9 @@ change its signature.
[moq-dev/rust2#675](https://github.com/moq-dev/rust2/pull/675); `Result` and `!` off a
return left the census with the narrowed stand-in.

## Waiting

- Maintainer: how `[T; N]` maps. Options: (a) a returned `[u8; N]` is bytes, as `Vec<u8>`
is, and a parameter `[u8; N]` is bytes whose length the glue checks, raising the
language's error on a mismatch; (b) a C struct `<prefix>_array_<T>_<N>` holding the
array, with each language's fixed-size form above it; (c) leave arrays refused and
keep this quest open until a consumer needs one. Recommended: (a), which reuses the
bytes every binding already has and clears both with no new type; any other element
type stays refused until one is needed.
Decided 2026-11-11 (maintainer, /quest-unblock): ✅ (b) `[T; N]` crosses as a C struct
`<prefix>_array_<T>_<N>` holding the array by value, with each language's fixed-size form above
it, such as Swift's `InlineArray` and a generated Kotlin type that checks its size: a proper
new type, with no allocation, that C compiles out (rejected: (a) bytes with a length check;
(c) leaving arrays refused). Element types beyond `u8` follow the same shape when one is
needed.
34 changes: 14 additions & 20 deletions quest/m1/ffi-refusals/std-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,19 @@ The rest of the original count lies in moq-tokio outside its session side (`NonZ

## Plan

Each item below waits on a maintainer decision; once decided, each is a change in
`ffi/src/collect.rs` and `ffi/src/glue.rs`, with the api fixture and its seven language
tests covering it.
Each decision below is a change in `ffi/src/collect.rs` and `ffi/src/glue.rs`, with the api
fixture and its seven language tests covering it.

## Waiting
Decided 2026-11-11 (maintainer, /quest-unblock):

- Maintainer: `Filter` collects at the call or stays refused. Collecting runs the predicate
on every item at the call, which a Rust caller that stops early would not run on the
rest; `ffi/fixtures/reject`'s `words` refuses a `Split` by a closure for that reason, so
collecting reverses it for both. The predicate is moq's own, pure, and a C caller can
only take every item, as a Rust caller that collects does. Recommended: collect, for
items whose drop runs nothing, so a refused item's drop never moves to the call.
- Maintainer: `serde_json`'s map iterators (`Iter`, `IntoIter`, `Keys`, `Values`) collect
as core's do, or stay refused. The exporter already trusts serde_json for `Json`, and
they only walk the map. Recommended: collect them.
- Maintainer: `std::time::Instant` exports as an opaque object, compared and copied by its
traits with no methods of its own, or as nothing. It has no portable form C could read,
and `into_std` and `from_std` only move it between kio and Rust. Recommended: the opaque
object, adding no API std lacks.
- Maintainer: `tracing::Level` exports as a plain enum of its five levels, with
`LevelFilter` as an `Option<Level>` whose `None` is `OFF`, or as its text, which a parse
could fail on. Recommended: the enum, which no input can make invalid.
- ✅ `Filter` collects at the call, for items whose drop runs nothing, reversing the `words`
fixture's `Split` refusal for the same case (rejected: staying refused). Collecting runs
the predicate on every item at the call, which a Rust caller that stops early would not;
that is accepted because the predicate is moq's own and pure, and a C caller can only take
every item, as a Rust caller that collects does.
- ✅ `serde_json`'s map iterators (`Iter`, `IntoIter`, `Keys`, `Values`) collect as core's do
(rejected: staying refused).
- ✅ `std::time::Instant` exports as an opaque object, compared and copied by its traits with
no methods of its own (rejected: exporting nothing).
- ✅ `tracing::Level` exports as a plain enum of its five levels, with `LevelFilter` as an
`Option<Level>` whose `None` is `OFF` (rejected: its text).
7 changes: 6 additions & 1 deletion quest/m4/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ Each names what the fork changes, the measured gain on moq, and its source.

Public API: none until the fork has a consumer.

Decided 2026-11-11 (maintainer, /quest-unblock): the reasons above are not yet enough to pay
for a fork; this ledger stays, and the maintainer decides again once a later report adds
evidence (rejected: fork now; deleting the ledger).

## Waiting

- Maintainer: decide that the reasons to fork above add up to enough to pay for
- Maintainer: once a report adds a reason or a measured gain to this ledger after the
2026-11-11 decision, decide whether the reasons to fork add up to enough to pay for
maintaining one. An agent can only gather the evidence; the cost of carrying a fork is
the maintainer's to weigh.