Skip to content

Deltic leg: the conformance suite runtime-linked on stock Deno - #149

Merged
lann merged 2 commits into
mainfrom
deltic-leg
Aug 10, 2026
Merged

Deltic leg: the conformance suite runtime-linked on stock Deno#149
lann merged 2 commits into
mainfrom
deltic-leg

Conversation

@lann

@lann lann commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

The polymorph-webrtc-datachannels entry in the jco-replacement migration (lann/deltic#14, scoped by lann/deltic#19 to the loopback matrix), following the pattern polymorph-websocket#40 and polymorph-tls#36 established — side by side with the existing targets, nothing jco-side changes.

What: a fifth loopback target deltic-deno — the same bare suite the jco leg transpiles, runtime-linked under deltic on stock Deno: no transpile step, no generated tree, no --experimental-wasm-jspi; async exports run on the callback ABI.

  • deltic-impl/ — the deltic-native host module: jco-impl/webrtc.js ported to deltic's embedder API (typed streams, WitError), upstreamed from deltic's ports/webrtc reference where the echo-demo already ran over real data channels. node-datachannel-backed (same addon + version as the jco legs); useWerift() keeps the pure-TS fallback available. Unit tests included.
  • conformance/driver-ct/deltic/run.ts — the child: mirrors jco/run.mjs exactly (RTC_CT_* env, --select prefix, results JSONL on stdout, the 512 KiB inbound bound exported via WEBRTC_MAX_INBOUND_BUFFER_BYTES); signaling.ts is the fetch mailbox ported from jco/signaling.js.
  • rtc-ct-driver — a Deno peer kind (loopback --kind deltic-deno): spawns deno run --allow-all --frozen --config <leg>/deno.json with the same child contract; solo stream + role-paired fold unchanged.

Found while porting (the high-yield-defect-discovery argument for consumer-suite runs): PeerConnection.close() closed only the native channels, so a post-close send could still see readyState === "open" under node-datachannel's asynchronous transition — solo/post-close-send was flaky under the callback ABI's faster await cadence (the case's 1000-send bound raced the 1 s close-drain window). close() now latches the DataChannel wrappers, per the WIT contract's "observed locally at once"; regression test added. The same latent race exists in jco-impl/webrtc.js (same drain, same addon — JSPI's slower awaits hide it); left untouched here for scope, happy to file separately.

Pinning: deltic release pre-10cc776 in two import maps (conformance/driver-ct/deltic/deno.json + deltic-impl/deno.json; integrity via committed deno.locks, enforced --frozen) and fetch-translator.ts (TAG + sha256 for the translator-shim release asset, cached under target/deltic/) — cross-checked at run time, incl. the module-identity constraint on the shared @deltic/runtime/embedder URL. Bump procedure in conformance/driver-ct/deltic/README.md.

Gates run locally (linux-arm64 dev box): just conformance — 5 loopback targets 37/37 each, all 13 interop directions, both aggregates, matrix-check against the regenerated committed matrix.md (interop matrix unchanged); just gha::rust-checks; just deltic-check (type-checks + the 10 unit tests, clean exit — the leg and the impl map bare node-datachannel so its cleanup() resolves and the addon's native workers release before exit).

CI: the conformance matrix job gains a pinned denoland/setup-deno step; deltic-check and conformance::run-deltic join gha::conformance-matrix. Note lann/deltic#21: deltic's own repo saw Deno×runner ICE timeouts for exactly this workload class while this repo's jco legs run green on the same image — this PR's Conformance run is the controlled A/B (same suite, same addon, Node vs Deno). If the deltic rows time out on the runner, that's the #21 delta isolated to the runtime, and I'll follow up here rather than absorb a red lane.

…tock Deno

The polymorph-webrtc-datachannels entry in the jco-replacement migration
(lann/deltic#14), following the pattern polymorph-websocket#40 and
polymorph-tls#36 established — side by side with the existing targets,
nothing jco-side changes.

- deltic-impl/: the deltic-native host module — jco-impl/webrtc.js ported
  to deltic's embedder conventions (WitError throws, typed streams),
  upstreamed from deltic's ports/webrtc reference where the echo-demo
  already ran over real data channels. node-datachannel-backed under
  stock Deno; unit tests included.
- conformance/driver-ct/deltic/: the leg — run.ts mirrors the jco child
  contract exactly (RTC_CT_* env, --select prefix, results JSONL on
  stdout, the 512 KiB inbound bound via WEBRTC_MAX_INBOUND_BUFFER_BYTES);
  signaling.ts is the fetch mailbox ported from jco/signaling.js.
- rtc-ct-driver: a Deno peer kind (loopback --kind deltic-deno) spawning
  the child with the leg's own deno.json/deno.lock (--frozen).
- Pinning: deltic release pre-10cc776 — raw.githubusercontent import maps
  + committed deno.locks for the module graph, sha256-pinned
  translator-shim release asset (fetch-translator.ts, cached under
  target/deltic/), pin consistency cross-checked at run time.
- Found while porting: PeerConnection.close() closed only the native
  channels, so a post-close send could still see readyState "open" under
  node-datachannel's asynchronous transition (solo/post-close-send was
  flaky under the callback ABI's faster await cadence). close() now
  latches the DataChannel wrappers (the WIT contract's "observed locally
  at once"); regression test added.

Matrix: 5 loopback targets, 37/37 each; interop matrix unchanged. CI
gains a pinned denoland/setup-deno step in the conformance matrix job;
deltic-check (type-check + unit tests) rides the same job.
…anup

Root-caused from this PR's first Conformance run (the deltic-check
failures) with a probe matrix on the runner (branch deltic-ice-debug,
run 31349796767; relates to lann/deltic#21):

- connectPair() started its candidate pumps before the offer/answer
  exchange and swallowed addIceCandidate rejections. On a slow runner,
  libdatachannel delivers the host candidates ~12 ms in — before the
  counterpart has its remote description — so every candidate was
  rejected (invalid-signaling) and lost (the stream delivers each
  candidate once), and wait-connected timed out at 20 s. The dev box
  simply won the race. Runtime-independent: a raw-API probe replicating
  the racy ordering fails identically under Node. The pumps now hold
  each candidate until the receiving peer has its remote description;
  on the runner the fixed tests pair in ~1 s.

- After every test passed, the process could still die 139 in native
  teardown: under deno test the runtime lives on past cleanup(), and
  the exit raced libdatachannel's thread joins (a hard exit right after
  cleanup() is clean — the standalone probes exit 0). The cleanup test
  now waits 1 s after cleanup() before the runner tears the isolate
  down.

Runner-validated: deno task test exits 0 on ubuntu-latest.
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