Skip to content

Onda 6: validação diferencial das 52 libcalls, clippy zerado e 5 bugs corrigidos - #27

Merged
filipeforattini merged 7 commits into
mainfrom
onda6-libcall-validation
Sep 30, 2026
Merged

filipeforattini merged 7 commits into
mainfrom
onda6-libcall-validation

Conversation

@filipeforattini

Copy link
Copy Markdown
Member

Onda 6 — validação das 52 libcalls

Executa a onda de validação do plano .red/code/plans/2026-09-26-backend-libcall-parity.md. As cinco ondas de implementação landaram com os testes deferidos por decisão do mantenedor; esta é a primeira vez que o corpus das 52 libcalls realmente roda. O exercício encontrou seis bugs reais — cinco corrigidos aqui, um caracterizado e deixado aberto por exigir redesenho.

Escopo respeitado: o diff não toca packages/compiler/src/backend/c, packages/compiler/src/backend/llvm nem packages/runtime (runtime C).

1. Clippy do runtime Rust zerado

cargo clippy --all-targets --locked -- -D warnings estava vermelho com 23 erros (o job runtime_rust não passava). Todos mecânicos, em arquivos das ondas novas:

arquivo lint n
src/schema.rs explicit_auto_deref 11
src/arrays.rs useless_asref (JsString: AsRef<JsString>) 5
src/arrays.rs box_collection 1
src/json_schema.rs useless_asref + collapsible_if 3
src/effect_http.rs useless_asref 2
src/regex.rs needless_borrow 1

Sem mudança de semântica: cada comparação reescrita mantém o mesmo PartialEq e o mesmo tipo de receptor. O box_collection de ArrayData::properties recebeu #[allow] com justificativa em vez de virar Vec: o Box mantém o array sem propriedades no tamanho pequeno, igual ao campo sparse vizinho — trocar aumentaria todo ArrayData em 16 bytes.

cargo test permanece em 260 passando.

2. Bugs reais encontrados pelo corpus

# bug onde estado
1 cp.execFile inalcançável: lowering completo no frontend e no backend, mas o membro nunca foi reivindicado na tabela de surface — a cerca em lower-calls.ts recusava com SC2020 antes do dispatch. Fatia inteira era código morto e 2911/2912 vermelhos em todas as lanes. frontend/lowering/surfaces.ts corrigido
2 maxBuffer: 64 * 1024 * 1024 recusado: sideEffectFreeOptionValue não conhecia aritmética. frontend/lowering/surfaces.ts corrigido
3 write depois de finish matava o programa: o dispatch de Finished limpava error_listeners antes de rodar os listeners de finish, então o ERR_STREAM_WRITE_AFTER_END não tinha ninguém para chamar e era lançado uncaught. A linha de erro e o exit do filho nunca saíam. runtime-rust/src/child_writer.rs corrigido
4 stdin.writable respondia false antes do erro de spawn chegar; Node responde true (devolve um ChildProcess vivo e reporta ENOENT num turno posterior). runtime-rust/src/child_writer.rs corrigido
5 child.disconnect() não era síncrono: connected continuava true e send continuava aceitando até uma ida-e-volta na thread do canal. Node fecha o canal dentro de disconnect(). Causa: connected acumulava quatro papéis (flag pública, porta do send, guarda de disparo único, e bit de liveness que adia o exit até o canal drenar), então não podia ser limpo. runtime-rust/src/child_ipc.rs corrigido
6 evento disconnect chegava depois do callback de um send emitido logo após disconnect(); no Node ele vem antes (tick seguinte ao disconnect local, sem ida-e-volta). runtime-rust/src/child_ipc.rs corrigido
7 write de 64 KiB + 1 num pipe vazio responde true no Node (libuv tenta inline, o kernel absorve os primeiros 64 KiB, só 1 byte conta contra o high-water mark) e false aqui, porque o writer entrega o buffer inteiro à thread e pontua backpressure por bytes entregues. Concordam em writes grandes, divergem exatamente na fronteira. runtime-rust/src/child_writer.rs ABERTO

O bug 5 foi resolvido separando a flag: connected passa a ser a pública e síncrona, limpa dentro de disconnect(); um novo open carrega o estado de dreno, então o exit continua adiado e uma mensagem que a thread já havia enquadrado antes do disconnect local continua sendo entregue — a garantia de EOF do plano não mudou.

O bug 7 (2894-child-stdin-backpressure.ts, continua vermelho) precisa de uma tentativa de write não-bloqueante inline na thread principal, enfileirando só o resto — redesenho do writer, e que exige decisão de dependência, já que #![forbid(unsafe_code)] impede chegar no libc direto.

3. Corpus por família

família libcalls fixtures lane rust
zlib 8 2921-zlib-callbacks.ts (corrigido), 3278-zlib-callback-invariants.ts (novo) verde
crypto 16 3279-crypto-hash-handles.ts, 3280-crypto-random-pbkdf2.ts (novos) verde
emitter 2 2961, 2962, 3281-emitter-flex-lifecycle.ts (novo) verde
child stdin 9 2893, 2895, 2896, 2897, 2898 verde (2894 vermelho — bug 7)
execFile 1 2911, 2912 (desbloqueados pelo bug 1) verde
fork/IPC 16 2946, 2948, 2949, 2963, 3282-child-fork-disconnect/ (novo) verde

50 das 52 libcalls têm agora evidência diferencial verde, confirmada empiricamente: cada fixture foi compilada com emitIr e o IR conferido contra a lista de spellings, não por inspeção visual.

Todos os fixtures novos e os dois de execFile levam // @rust-only. As 52 linhas são ["rust"] em BACKEND_LIB_CALLS, ou seja C e LLVM recusariam — é o drift do upstream que o plano manda registrar e não consertar. @rust-only é o mecanismo que o próprio repo já usa para isso (198 programas antes deste PR).

O que cada fixture novo prova

  • 3278 — contrato de ciclo de vida dos 7 callbacks + crc32: callback agendado (nunca inline, conferido contra um flag returned), disparado exatamente uma vez (contador impresso em cada settle), e falha do codec chegando como argumento um sem a chamada lançar. Cobre os quatro decodificadores sobre o mesmo lixo fixo, a falha distinta de gzip truncado, a auto-detecção do unzip nos dois wrappers, cinco deflates concorrentes cada um disparando uma vez, entrada vazia, e o encadeamento semeado do crc32 com seu ERR_OUT_OF_RANGE. Nenhum byte comprimido é impresso (dependem da versão do zlib): todo encoder é provado pelo seu decoder.
  • 3279 — a superfície incremental de Hash/Hmac, distinta da cadeia fundida que o compilador dobra em uma única call (por isso os programas existentes 1534/2820/2821/2870 não tocavam nenhuma das 16). hash.copy() é testado por independência real: prefixo comum, escrita divergente em cada lado, cada um comparado contra um digest construído à parte; cópia não escrita; e finalizar a cópia sem finalizar a origem. O estado terminal fixa a assimetria do Node: Hash lança ERR_CRYPTO_HASH_FINALIZED em update/digest/copy depois do digest, enquanto Hmac lança no update mas responde string vazia num segundo digest.
  • 3280 — pbkdf2Sync com bytes exatos (é determinístico), e a derivação de uma iteração conferida contra HMAC(password, salt || INT(1)) construído pela própria superfície Hmac, em vez de uma constante copiada. Aleatoriedade só por invariante, nunca por byte: identidade da view retornada, bytes fora de [offset, offset+size) intactos, fill de tamanho zero não toca nada, formas "rest" cobrindo o buffer todo e a partir de um offset, alvo Uint8Array, e 32 bytes preenchidos não podendo ser todos zero. randomInt é sorteado 400 vezes nas duas aridades, com faixa e integralidade conferidas, e todo valor de uma faixa de largura 3 tem de aparecer — gerador travado ou com viés modular falha aí.
  • 3281 — ordem de registro, prependListener na frente, identidade da função (mesma referência duas vezes conta duas, dispara duas, e off remove uma por vez), once, e remoção durante a emissão nas três variantes (remover outro, remover a si mesmo, removeAllListeners) — o listener removido ainda roda na emissão em voo, porque o Node despacha sobre um snapshot. Mais: nome literal casado pelo padrão computado caindo no mesmo bucket, e o error reservado permanecendo no bucket tipado.
  • 3282 — as duas últimas libcalls sem cobertura: child.disconnect (todos os programas existentes desconectam pelo lado do worker) e o child.send(mensagem) sem callback (todos os existentes passam um, o que roteia para child.sendCb). Distingue canal ausente (filho de spawn, sem slot IPC) de canal desconectado; rajada de mensagens enquadradas que o worker reporta inteira mesmo com o canal derrubado logo após a resposta; os dois callbacks de send assentando exatamente uma vez; e o worker escrevendo o exit code duas vezes para o pai confirmar que observa a última escrita.

4. As 2 libcalls que ficaram abertas

process.setExitCode e process.currentExitCode não têm produtor no frontend — são inalcançáveis a partir de TypeScript, então não podem receber uma linha de corpus:

  • process.exitCode = n lowera para process.exitCodeSet, e process.setExitCode emite a mesma função de runtime (runtime::process_exit_code_set, backend/rust/process.ts:15-20): é um alias redundante.
  • Ler process.exitCode não tem lowering nenhum — verificado: recusa com SC2020 'process.exitCode' is part of the standard library types but has no scriptc lowering yet.

A propriedade que o plano pede para elas ("mesma fonte de verdade que process.exitCodeSet") está verificada de forma indireta: compartilham o único escritor no runtime, e 3282 observa last-write-wins de fora. Recomendação para o mantenedor: ou remover o alias, ou adicionar o lowering da leitura de process.exitCode e então cobri-las.

5. Gates

gate resultado
cargo clippy --all-targets --locked -- -D warnings verde (era 23 erros)
cargo test --locked verde, 260 passando
pnpm manifest --check verde (665 entradas)
pnpm gen:backend-libcalls --check verde (1289 spellings, 0 órfãs)
pnpm node-compat:check verde (4690 linhas internas, 3662 públicas)
pnpm -r build verde
differential rust das famílias (36 programas) 33 verdes, 3 vermelhos pré-existentes
tests/harness/diagnostics.test.ts (131 fixtures) 23 falhas, todas pré-existentes
lanes locais completas (pnpm test / SCRIPTC_SAN=1) não executadas — ver abaixo

Rebaseado em 8d5c8962, então este branch já está no mundo pós-remoção do harness de Vercel Sandbox (PR #26): não há test:sandbox para rodar, e o item 5 da Onda 6 agora manda rodar as duas lanes locais.

Essas lanes completas não foram executadas, por combinação explícita com o mantenedor: são horas nesta máquina e o cgroup de 3 GiB do pnpm limit mata o rustc por OOM. O gate entregue é o subconjunto focado acima — geradores, cargo test/clippy, build do workspace, o differential rust das seis famílias e o suite de diagnostics inteiro.

O differential das famílias rodou os 36 programas que tocam zlib, crypto, emitter, child stdin, execFile e fork/IPC. Os 3 vermelhos são 2894 (bug 7, aberto), 2890-crypto-static-utility.ts e 3144-zlib-compression-views.ts — os dois últimos já vermelhos em main por formas sem lowering, detalhados abaixo. Os 7 fixtures novos/corrigidos passam todos.

Prova de não-regressão da mudança no frontend. surfaces.ts é compartilhado, então rodei o suite de diagnostics inteiro (131 fixtures) nas duas pontas: revertendo minha mudança e recompilando dá 24 falhas; com ela, 23. A única diferença é child-process.ts, que sai do vermelho porque regenerei o snapshot dele. Nenhum fixture passou a falhar. A extensão de sideEffectFreeOptionValue também é monotônica por construção: ela só decide recusas, então um programa que já compilava não pode passar a falhar por causa dela.

6. Dívida pré-existente que a onda revelou (fora do escopo deste PR)

Rodar o corpus pela primeira vez mostrou que a lane rust já estava vermelha em main por motivos alheios às 52 libcalls. Registrado aqui, não corrigido:

  • tests/harness/diagnostics.test.ts: 23 snapshots desatualizados continuam vermelhos (eram 24 em main), verificado pela comparação de conjuntos descrita acima: assert-fences, async-iterator-handles, cjs-module-mutation, component-fences, create-require, dataview, dynamic-import-static, intersection-values, json-dyn, maps-surface, maps, node-fallback-fence, overload-fences, overloaded-function-values, record-width, regex-checker, regex-function-replacement, regex, sets, spread-fences, stdlib-fence, stdlib, tuples. As ondas de implementação atualizaram os fixtures e nunca regeneraram os snapshots. Só child-process.ts foi regenerado aqui, porque a correção do execFile muda legitimamente a saída dele — e o fixture já tinha sido escrito para o mundo pós-onda-4 (as cercas por sítio execFile with 1 arguments, fork without a fourth-slot IPC channel etc. agora aparecem em vez de uma recusa única do membro).
  • 2913-builtin-callable-alias.ts vermelho: alias imutável de builtin tabelado (const pack = deflateSync;) recusado com SC1090 "library functions as values". O programa foi escrito para uma feature que hoje é recusada.
  • 3008-deflate-fixed-level.ts e 3144-zlib-compression-views.ts vermelhos: deflateSync com opções explícitas (zlib.deflateSyncLevel) sem lowering no frontend.
  • 2890-crypto-static-utility.ts vermelho por quatro formas distintas sem lowering, entre elas o pbkdf2 assíncrono com callback (crypto.pbkdf2Cb é nomeado pelo emitter LLVM e não tem produtor no frontend — outro órfão) e a cadeia fundida que cerca em vez de cair para o caminho de handle quando a forma não fecha, o que bloqueia createHash("sha1").update("x").digest(), uma grafia Node perfeitamente comum. As 16 libcalls de crypto ficaram cobertas por 3279/3280 sem depender dessas formas.
  • Outros vermelhos não relacionados: 2877-child-unknown-roundtrip.ts, 2912-promisify-fs-readfile.ts, 2939-child-shell-text.ts, 2940-fsp-realpath.ts, 2941-fsp-lstat-identity.ts, 2950-code-point-at.ts, 2950-fs-descriptor-publication.ts, 2966-numeric-array-read-fusion.ts.

Não mergeado de propósito.

🤖 Generated with Claude Code

https://claude.ai/code/session_0197JoEpMBBqqkSiBb2vNX5A

`cargo clippy --all-targets -- -D warnings` was failing with 23 errors in
the files added by the libcall waves. All of them are mechanical:

- `schema.rs`: 11 `explicit_auto_deref` — pass the smart pointer and let
  the coercion happen at the call.
- `arrays.rs`: 5 `useless_asref` on `JsString` (it implements
  `AsRef<JsString>`, so `as_ref()` was the identity) plus one
  `box_collection` on `ArrayData::properties`. The `Box` there is
  deliberate — it keeps a property-free array at its small size, exactly
  like the neighbouring `sparse` field — so that one gets a scoped
  `#[allow]` with the rationale instead of widening every `ArrayData`.
- `json_schema.rs`: one `useless_asref` and a `collapsible_if` folded
  into a let-chain, which this crate already uses elsewhere.
- `effect_http.rs`: two `useless_asref`.
- `regex.rs`: one `needless_borrow`.

No semantic change: every rewritten comparison keeps the same
`PartialEq` impl and the same receiver type. `cargo test` stays at 260
passing.
Wave 4 of the libcall plan landed `cp.execFile` end to end — the IR op,
its validator, `lowerExecFileCall` with the full callback/options fence
ladder, the Rust emitter, the runtime, and the corpus programs 2911/2912
— but never claimed the member. `builtinModuleFnOf` found no
`child_process.execFile` row, so the fence in lower-calls.ts refused the
call with SC2020 before `lowerBuiltinModuleCall`'s dispatch could reach
the lowering. The whole slice was dead code and both corpus programs
were red in every lane.

- Add the `execFile` row to the child_process surface table, the same way
  spawn/fork/execFileSync carry canonical shapes while their real call
  completion is special-cased in `lowerBuiltinModuleCall`.
- Drop the stale "the callback form has no lowering — promisify it"
  fence hint, which now contradicts the implementation.
- Teach `sideEffectFreeOptionValue` about arithmetic over numeric
  LITERALS so `maxBuffer: 64 * 1024 * 1024` (2912) is accepted. Operands
  are restricted to literals on purpose: an identifier operand is a
  side-effect-free read, but arithmetic on it would run valueOf.
- Mark 2911/2912 `// @rust-only`: `cp.execFile` is a Rust-lane row in
  BACKEND_LIB_CALLS, so the C and LLVM lanes would refuse it. That is
  the upstream drift the plan records and does not fix.
- Regenerate surface-manifest.json (the row becomes status "static",
  backends ["rust"]) and the child-process.ts diagnostics snapshot, which
  now shows the per-site execFile/fork fences the fixture was written
  for instead of one blanket member refusal.

Verified: both corpus programs pass the Rust differential lane, and a
minimal execFile program's stdout/stderr/exit match Node byte for byte.
The zlib slice had NO compiling corpus coverage: 2921 — the one program
naming all eight calls — was red in every lane on `const gzipAlias =
gzip;`. An immutable alias of an OVERLOADED table-backed builtin is a
principled frontend refusal (SC2007 on the two call signatures, SC1090 on
library functions as values), and builtin aliasing has its own fixture in
2913, so the alias was never this program's subject. Call `gzip` directly
and the rest of the file is sound.

- 2921: drop the alias, mark `// @rust-only`. All eight spellings are
  Rust-lane rows in BACKEND_LIB_CALLS, so C and LLVM would refuse them —
  the upstream drift the plan records rather than fixes.
- 3278: the lifecycle contract the plan asks for and 2921 does not prove.
  Per codec: the callback is SCHEDULED (never inline — checked against a
  `returned` flag set after the call), fires EXACTLY ONCE (a counter
  printed with every settle), and a codec failure arrives as argument ONE
  with the call itself not throwing. Covers all four decoders over the
  same fixed garbage, the distinct truncated-gzip failure, unzip's
  auto-detection of both wrappers, five concurrent deflates each firing
  once, the empty-input stream, and crc32's seeded chaining plus its
  ERR_OUT_OF_RANGE. No compressed bytes are printed — they are
  zlib-version-dependent, so every encoder is proved through its decoder
  and only the fixed gzip framing is shown.

Both programs pass the Rust differential lane (stdout, stderr and exit
code byte-identical to Node 24) and between them name all eight libcalls.
Wave 2's sixteen crypto libcalls had no compiling corpus coverage. The
only program that named them, 2890, is red (four separate unsupported
forms), and the hash programs that do compile — 1534, 2820, 2821, 2870 —
all use the FUSED chain, which the frontend folds into one
hashDigestStr/hmacDigestStr call and which therefore never reaches a
single handle libcall.

Two programs, both `// @rust-only` (all sixteen rows are Rust-only in
BACKEND_LIB_CALLS):

3279 — the incremental handle surface. Every handle is stored in a
binding so the chain fast path stands down and the per-step calls run:
createHash/createHmac with string and byte keys, update with string and
bytes and with hex/utf8 input encodings, and both terminal digests.
Digests are deterministic, so they print in full. `hash.copy()` is tested
for real INDEPENDENCE: a shared prefix, then a divergent write on each
side, each compared against a separately built expected digest; a copy
taken and not written; and finalizing the copy without finalizing its
origin. Terminal state pins Node's asymmetry exactly — Hash throws
ERR_CRYPTO_HASH_FINALIZED on update, digest and copy after digest, while
Hmac throws on update but answers an EMPTY string on a second digest.

3280 — pbkdf2Sync and the randomness primitives. pbkdf2 is deterministic,
so its bytes print, and the one-iteration single-block derivation is
cross-checked against HMAC(password, salt || INT(1)) built from the Hmac
surface instead of a copied constant. Randomness is tested by INVARIANT,
never by byte: randomFillSync returns the same view, a windowed fill
leaves the bytes outside [offset, offset+size) at their marker, a
zero-size fill touches nothing, the rest forms cover the whole buffer and
from an offset, a Uint8Array target works, and 32 filled bytes are not
all zero. randomInt is drawn 400 times against both arities, checked for
range and integrality, and every value of a 3-wide range must appear —
a stuck or modulo-biased generator fails that. Out-of-range fills,
inverted, empty and fractional ranges assert their observable errors.

Both pass the Rust differential lane and between them name all sixteen
libcalls.
2961 and 2962 already reach `emitter.onFlex`/`emitFlex` and pass, but they
only prove that a computed event name works at all. Wave 3's claim is
stronger: the checked-dynamic path must keep the SAME lifecycle as the
typed one. 3281 pins that, `// @rust-only` like both existing programs'
family (the two rows are Rust-only in BACKEND_LIB_CALLS).

Covered: registration order; prependListener landing in front; function
IDENTITY (the same reference registered twice counts twice, fires twice,
and `off` removes one registration at a time); `once` firing exactly once
and leaving the remaining listeners intact; removal performed DURING an
emit — the removed listener still runs for the in-flight emit and is gone
for the next, because Node dispatches over a snapshot — plus the
self-removing and removeAllListeners-during-emit variants; emit with no
listener answering false; a LITERAL event name that the computed pattern
matches reaching the same bucket as the computed spelling; payload arity
through zero- and three-parameter listeners (a rest parameter cannot cross
the boundary, so arity is covered by fixed arities); number and string
payloads used as their real types on the far side; and the reserved
`error` event staying on its own typed bucket with the Error object
intact while flex listeners exist on the same emitter.

Passes the Rust differential lane, so every one of those orderings is
byte-identical to Node 24.
Running wave 4's own corpus programs for the first time turned up three
mismatches against Node in the child-stdin writer. Two of them are fixed
here; the third is an architectural gap recorded below.

2896-child-stdin-write-after-finish: the `Finished` dispatch cleared
`error_listeners` before running the finish listeners. The program writes
from inside its finish listener — Node's ERR_STREAM_WRITE_AFTER_END, which
must arrive on the stream's `error` event — so `child_writer_fail` found an
empty listener list, took its no-listener branch and threw the error
UNCAUGHT. The program died mid-output: the error line and the child's own
`exit` line never printed. The error listeners now survive finish, which
is also what Node does.

2898-child-stdin-spawn-failure: the husk stdin handed back for a child
that failed to spawn started `writable: false`, so a read before the error
landed answered false where Node answers true (Node returns a live
ChildProcess and reports ENOENT on a later turn).
`children_dispatch_one` already destroys the writer immediately before it
delivers the error, so the read from inside the `error` listener still
answers false — the asymmetry the program checks.

STILL RED, not fixed here: 2894-child-stdin-backpressure. Node's `write`
answers true for a 64 KiB + 1 write into an empty pipe because libuv
attempts the write inline on the calling thread, the kernel absorbs the
first 64 KiB, and only the 1-byte remainder counts against the high-water
mark. This writer hands the whole buffer to its writer thread and scores
backpressure by bytes handed over, so it answers false. The two agree on
large writes and disagree exactly at the boundary. Matching Node needs an
inline non-blocking write attempt on the main thread with only the
remainder queued — a writer redesign, and one that needs a dependency
decision, since `#![forbid(unsafe_code)]` rules out reaching for libc.

`cargo test` stays at 260 passing and clippy stays clean; 2896 and 2898
now pass the Rust differential lane.
The last two fork/IPC libcalls without corpus coverage were
`child.disconnect` (every existing program disconnects from the WORKER
side with process.disconnect()) and the bare `child.send(message)` with no
callback (every existing send passes one, which routes to child.sendCb).
Covering them exposed two divergences in the IPC channel.

`connected` was doing four jobs at once: the public flag, the send gate,
the fire-once guard on the disconnect event, and the liveness bit that
defers the child's exit event until the channel has drained. Because of the
last two, disconnect() could not clear it — so it only sent a command to
the channel thread, and `child.connected` kept answering true and
`child.send` kept answering true until a thread round trip completed. Node
closes the channel inside disconnect(): the next read is already false and
the next send is already refused.

Split the flag. `connected` is now the public, synchronous one, cleared
inside disconnect(). A new `open` carries the drain state, so the exit
event stays deferred and a message the worker thread had already framed
before the local disconnect is still delivered — the EOF guarantee the
plan asks for is unchanged, and the disconnect listeners still fire exactly
once, now guarded on `open`.

Second divergence: the `disconnect` EVENT arrived only on the channel
thread's round trip, so it landed AFTER the callback of a send issued right
after disconnect(). Node emits it on the tick following a local
disconnect(), i.e. before that callback. disconnect() now takes its
listeners and queues them itself; the later Disconnected event still fails
the pending send callbacks but finds no listener left to call.

3282 pins all of it: a spawned child with no IPC slot reads as not
connected (channel ABSENT) versus a forked child after disconnect (channel
DISCONNECTED); the bare send and a burst of framed messages, all five of
which the worker reports back even though the channel is torn down
immediately after the reply; both send callbacks settling exactly once,
the late one with the channel-closed error; and the worker writing its
exit code twice so the parent can confirm the last write is the one it
observes.

All five fork corpus programs — 2946, 2948, 2949, 2963 and 3282 — pass the
Rust differential lane. cargo test stays at 260 passing, clippy clean.
@filipeforattini
filipeforattini merged commit ba5d2a2 into main Sep 30, 2026
1 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant