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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cargo test -p translator-shim -p bindgen -p testgen
(cd wasi-shims && deno task test)
(cd ct-runner && deno task test)
(cd ports/websocket && deno task test) # + deno task conformance (spawns their echod)
deno run --allow-read tools/smoke-tls/run.ts --exec # polymorph-tls suite (issue #18)
(cd ports/webcrypto && deno test --allow-read tests/)
(cd ports/webrtc && deno test -A webrtc.test.ts)
deno run -A tools/browser/run-lane.ts chromium # firefox / webkit likewise
Expand Down
20 changes: 13 additions & 7 deletions docs/consumers.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ Their jco blockers map one-for-one onto this project's proven strengths
most important interfaces are exactly these. The embedder-api contract
carries paper signatures for a representative WASI slice; the
`wasi-shims/` package is the executable check.
- **Their suites become our gates.** This family surfaced at least five
distinct jco defect classes that no WAST corpus expresses (long-lived
composed workloads, background pumps, cross-task wakeups, codegen-shape
triggers) — and two deltic runtime defects (smoke-c0's R-1/R-2) the same
way. The polymorph matrices and experiment-mosh gates run as release
gates ([#6](https://github.com/lann/deltic/issues/6)) —
necessary-not-sufficient discipline applied to ourselves.
- **Their suites are engine sanity checks, not gates** (operator ruling,
2026-08-10; supersedes the earlier "their suites become our gates"
posture and the release-gate framing of the now-closed
[#6](https://github.com/lann/deltic/issues/6)). This family surfaced at
least five distinct jco defect classes that no WAST corpus expresses
(long-lived composed workloads, background pumps, cross-task wakeups,
codegen-shape triggers) — and five deltic runtime defects the same way
(smoke-c0's R-1/R-2; the tls smoke's three,
[#18](https://github.com/lann/deltic/issues/18)) — so running them is
high-yield. But everything on both sides is unstable and co-evolves in
tandem: a consumer-suite delta is a finding to triage, never a blocker
for upstreaming or release.
- **What replacing jco does not replace**: componentize-js/-go (guest
production — out of scope per [architecture.md §2](architecture.md); their
output components are ordinary inputs to us) and the wasmtime host legs
Expand Down Expand Up @@ -89,6 +94,7 @@ Reference implementations developed here, pending upstreaming
| `exams/iroh-endpoint` | the endpoint exit exam | 5/5: bind+identity, relay echo, WebRTC upgrade, jco#11/#13 assertions, teardown |
| `ct-runner` | L3 runner for the polymorph-test L1 contract | golden-tested L4 JSONL; drives the websocket suite |
| `tools/smoke-c0` | C0 smoke legs + report | legs 1–4 (`REPORT.md`) |
| `tools/smoke-tls` | polymorph-tls conformance under deltic ([#18](https://github.com/lann/deltic/issues/18)) | translate 8/8; suites 6/6 applicable green per target (named xfails: tag-gating [#25](https://github.com/lann/deltic/issues/25), callback-null-context [#24](https://github.com/lann/deltic/issues/24)) |

Deferred consumer surfaces: experiment-mosh deep E2E
([#2](https://github.com/lann/deltic/issues/2)), webcrypto family completion
Expand Down
4 changes: 2 additions & 2 deletions harness/browser/expectations/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export const chromium: LaneExpectation = {
totals: {
commands: 1395,
executed: 1349,
passed: 1250,
passed: 1254,
failed: 0,
xfail: 99,
xfail: 95,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
4 changes: 2 additions & 2 deletions harness/browser/expectations/firefox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export const firefox: LaneExpectation = {
totals: {
commands: 1395,
executed: 1349,
passed: 1250,
passed: 1254,
failed: 0,
xfail: 99,
xfail: 95,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
8 changes: 6 additions & 2 deletions harness/browser/expectations/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1297,9 +1297,13 @@ export const webkit: LaneExpectation = {
executed: 1349,
// 18 commands moved from xfail to passed when FINDING M3A-1 was fixed in
// the runtime and its entries left this file.
passed: 1076,
// +4 more (async/drop-cross-task-borrow:305,307, async/passing-resources
// :175,176) when the #18 tls-smoke fixes pruned their shared xfail
// entries — arithmetic update, NOT re-measured (webkit is not runnable
// on this dev host); the post-merge webkit lane is the check.
passed: 1080,
failed: 0,
xfail: 273,
xfail: 269,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
4 changes: 2 additions & 2 deletions harness/shell/expectations/jsc-pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export const jscPinned: ShellLaneExpectation = {
totals: {
commands: 1395,
executed: 1349,
passed: 1250,
passed: 1254,
failed: 0,
xfail: 99,
xfail: 95,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
6 changes: 3 additions & 3 deletions harness/shell/expectations/jsc-trunk.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// JSC trunk lane expectation — a findings lane (best-effort, non-gating;
// issue #22). Seeded from the first successful CI run (2026-08-09, GH
// Actions ubuntu-24.04 x64, bundle rev `318852@main` built 2026-08-08):
// **EXACT Deno-lane parity** — 1250 passed / 0 failed / 99 xfail (the Deno
// **EXACT Deno-lane parity** — 1254 passed / 0 failed / 95 xfail (the Deno
// lane's own classes, identical classification), zero jsc-specific deltas,
// and the full capability matrix true: JSPI (round trip verified),
// multi-memory, wasm-GC, exception-handling, memory64, tail-calls,
Expand Down Expand Up @@ -63,9 +63,9 @@ export const jscTrunk: ShellLaneExpectation = {
totals: {
commands: 1395,
executed: 1349,
passed: 1250,
passed: 1254,
failed: 0,
xfail: 99,
xfail: 95,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
4 changes: 2 additions & 2 deletions harness/shell/expectations/sm-nightly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export const smNightly: ShellLaneExpectation = {
totals: {
commands: 1395,
executed: 1349,
passed: 1250,
passed: 1254,
failed: 0,
xfail: 99,
xfail: 95,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
6 changes: 3 additions & 3 deletions harness/shell/expectations/sm-pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// RESULT (2026-08-09, seed run — this host is aarch64; the release-153
// jsshell ships an aarch64 build, sha256-verified via pins.json):
// **the lane runs the full corpus, EXACT Deno-lane parity.** All 59 files,
// 1395 commands, 1349 executed, 1250 passed, 0 failed, 99 xfail, 41
// 1395 commands, 1349 executed, 1254 passed, 0 failed, 95 xfail, 41
// pending-runtime, 0 pending-capability, 5 unsupported-directive. Zero
// deltas, zero stale xfails, zero unexpected failures. Capability matrix:
// jspi = {suspending: true, promising: true, roundTrip: true}, multiMemory
Expand Down Expand Up @@ -51,9 +51,9 @@ export const smPinned: ShellLaneExpectation = {
totals: {
commands: 1395,
executed: 1349,
passed: 1250,
passed: 1254,
failed: 0,
xfail: 99,
xfail: 95,
pendingRuntime: 41,
pendingCapability: 0,
unsupportedDirective: 5,
Expand Down
36 changes: 5 additions & 31 deletions harness/src/xfail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,20 +284,8 @@ export const XFAIL: XfailEntry[] = [
// flip: a start-function SuspendError maps to "cannot block a synchronous
// task before returning"); entry pruned. ---
// --- async/drop-cross-task-borrow.json: root cause: FACT-ASYNC ---
{
file: "async/drop-cross-task-borrow.json",
line: 305,
reason:
"observed: AssertionError: transfer-borrow outside an " +
"enter-sync-call/exit-sync-call bracket",
},
{
file: "async/drop-cross-task-borrow.json",
line: 307,
reason:
"observed: AssertionError: transfer-borrow outside an " +
"enter-sync-call/exit-sync-call bracket",
},
// lines 305/307 GREEN after the #18 tls-smoke fixes (FACT [async-start]
// borrow window + ResourceTypeInfo unification); entries pruned.
{
file: "async/drop-cross-task-borrow.json",
line: 309,
Expand All @@ -321,23 +309,9 @@ export const XFAIL: XfailEntry[] = [
// --- async/futures-must-write.json: root cause: STREAMS ---
// --- async/partial-stream-copies.json: GREEN under jspi auto-detection
// (M2 flip); entry pruned. ---
// --- async/passing-resources.json: root cause: STREAMS ---
{
file: "async/passing-resources.json",
line: 175,
reason:
"observed: TypeError: Converting circular structure to JSON " +
"--> starting at object with constructor 'Object' | " +
"property 'rt' -> object with constructor " +
"'ResourceTypeInfo' | property",
},
{
file: "async/passing-resources.json",
line: 176,
reason:
"observed: Error: expected trap \"unknown handle index 3\", " +
"got \"wasm trap: wasm `unreachable` instruction executed\"",
},
// --- async/passing-resources.json: lines 175/176 GREEN after the #18
// tls-smoke fixes (cycle-safe structural ValType equality + token
// unification); entries pruned. ---
// --- async/same-component-stream-future.json: root cause: STREAMS ---
// --- async/sync-barges-in.json: GREEN under jspi auto-detection (M2
// flip); entry pruned. ---
Expand Down
31 changes: 19 additions & 12 deletions runtime/src/cabi/async_values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import { assert_, trapIf } from "./trap.ts";
import type { LiftLowerContext } from "./context.ts";
import type { BorrowType, OwnType, ValType } from "./types.ts";
import { contains } from "./types.ts";
import { contains, fmtValType } from "./types.ts";
import {
CopyState,
ErrorContext,
Expand Down Expand Up @@ -123,13 +123,18 @@ export function lowerStream(
// `ValType` (typed derivation is bindgen's job), so this is the first point
// at which a mismatch against the guest's declared `stream<T>` can be
// caught — and a silent mismatch would corrupt every copy, since the
// element type is what sizes and lifts the buffer.
// element type is what sizes and lifts the buffer. `fmtValType`, not
// `JSON.stringify`: the latter throws on resource-bearing element types
// (cabi/types.ts `valTypeEqual` contract note) — and as a template-literal
// argument it was evaluated even when the assertion PASSED.
const declared = (t as { element?: ValType | null }).element ?? null;
assert_(
sameElemType(v.t, declared),
`stream element type mismatch: host end carries ` +
`${JSON.stringify(v.t)}, callee expects ${JSON.stringify(declared)}`,
);
if (!sameElemType(v.t, declared)) {
assert_(
false,
`stream element type mismatch: host end carries ` +
`${fmtValType(v.t)}, callee expects ${fmtValType(declared)}`,
);
}
const inst = cx.inst;
assert_(inst !== null, "stream lower requires a component instance");
(v as { boundStore?: unknown }).boundStore ??=
Expand All @@ -150,11 +155,13 @@ export function lowerFuture(
);
assert_(!containsBorrow(t), "future may not contain a borrow");
const declared = (t as { element?: ValType | null }).element ?? null;
assert_(
sameElemType(v.t, declared),
`future element type mismatch: host end carries ` +
`${JSON.stringify(v.t)}, callee expects ${JSON.stringify(declared)}`,
);
if (!sameElemType(v.t, declared)) {
assert_(
false,
`future element type mismatch: host end carries ` +
`${fmtValType(v.t)}, callee expects ${fmtValType(declared)}`,
);
}
const inst = cx.inst;
assert_(inst !== null, "future lower requires a component instance");
(v as { boundStore?: unknown }).boundStore ??=
Expand Down
137 changes: 137 additions & 0 deletions runtime/src/cabi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,140 @@ export function contains(
return p(d);
}
}

// ---------------------------------------------------------------------------
// Structural ValType equality and display
// ---------------------------------------------------------------------------

/**
* Structural `ValType` equality.
*
* CONTRACT (bugfix, TRACK C2-D; generalized during the #18 tls smoke): naive
* `JSON.stringify(a) === JSON.stringify(b)` recurses into `own`/`borrow`'s
* `ResourceTypeInfo` — a class whose `impl` field is documented "Compared by
* object identity everywhere" (see `ResourceTypeInfo` above) and which cycles
* back to the owning instance state (`impl.handles` holds live resource
* tables that reference their types), so `JSON.stringify` throws
* `TypeError: Converting circular structure to JSON` on ANY type containing
* `own<R>`/`borrow<R>` at any depth. First hit by `task.return` result types
* (C2-D, polymorph-test's `list<own<test-case>>`), then by stream/future
* element types (polymorph-tls streams carrying resource-bearing payloads).
* Object-identity types (`ResourceTypeInfo`) are compared by reference, per
* the documented invariant.
*/
export function valTypesEqual(a: ValType[], b: ValType[]): boolean {
return a.length === b.length && a.every((t, i) => valTypeEqual(t, b[i]));
}

export function valTypeEqual(a: ValType, b: ValType): boolean {
if (a === b) return true;
if (a.kind !== b.kind) return false;
switch (a.kind) {
case "list": {
const bb = b as typeof a;
return a.length === bb.length && valTypeEqual(a.element, bb.element);
}
case "record": {
const bb = b as typeof a;
return a.fields.length === bb.fields.length &&
a.fields.every((f, i) =>
f.label === bb.fields[i].label && valTypeEqual(f.type, bb.fields[i].type)
);
}
case "tuple": {
const bb = b as typeof a;
return a.elements.length === bb.elements.length &&
a.elements.every((e, i) => valTypeEqual(e, bb.elements[i]));
}
case "variant": {
const bb = b as typeof a;
return a.cases.length === bb.cases.length &&
a.cases.every((c, i) => {
const other = bb.cases[i];
if (c.label !== other.label) return false;
if (c.type === null || other.type === null) return c.type === other.type;
return valTypeEqual(c.type, other.type);
});
}
case "enum":
case "flags": {
const bb = b as typeof a;
return a.labels.length === bb.labels.length &&
a.labels.every((l, i) => l === bb.labels[i]);
}
case "option": {
const bb = b as typeof a;
return valTypeEqual(a.type, bb.type);
}
case "result": {
const bb = b as typeof a;
if ((a.ok === null) !== (bb.ok === null)) return false;
if ((a.error === null) !== (bb.error === null)) return false;
return (a.ok === null || valTypeEqual(a.ok, bb.ok!)) &&
(a.error === null || valTypeEqual(a.error, bb.error!));
}
case "map": {
const bb = b as typeof a;
return valTypeEqual(a.key, bb.key) && valTypeEqual(a.value, bb.value);
}
case "own":
case "borrow": {
const bb = b as typeof a;
// Object-identity type (documented invariant): reference equality only.
return a.rt === bb.rt;
}
case "stream":
case "future": {
const bb = b as typeof a;
if ((a.element === null) !== (bb.element === null)) return false;
return a.element === null || valTypeEqual(a.element, bb.element!);
}
case "error-context":
return true;
default:
// Remaining kinds (primitives) carry no extra fields beyond `kind`.
return true;
}
}

/**
* Cycle-safe display form for diagnostics. `JSON.stringify(t)` is UNSAFE on
* any resource-bearing type (see `valTypeEqual`'s contract note); this prints
* the structural shape and elides `ResourceTypeInfo` identities.
*/
export function fmtValType(t: ValType | null): string {
if (t === null) return "_";
switch (t.kind) {
case "list":
return t.length === undefined
? `list<${fmtValType(t.element)}>`
: `list<${fmtValType(t.element)}, ${t.length}>`;
case "record":
return `record{${t.fields.map((f) => `${f.label}: ${fmtValType(f.type)}`).join(", ")}}`;
case "tuple":
return `tuple<${t.elements.map(fmtValType).join(", ")}>`;
case "variant":
return `variant{${
t.cases.map((c) => c.type === null ? c.label : `${c.label}(${fmtValType(c.type)})`)
.join(", ")
}}`;
case "enum":
return `enum{${t.labels.join(", ")}}`;
case "flags":
return `flags{${t.labels.join(", ")}}`;
case "option":
return `option<${fmtValType(t.type)}>`;
case "result":
return `result<${fmtValType(t.ok)}, ${fmtValType(t.error)}>`;
case "map":
return `map<${fmtValType(t.key)}, ${fmtValType(t.value)}>`;
case "own":
case "borrow":
return `${t.kind}<resource>`;
case "stream":
case "future":
return `${t.kind}<${fmtValType(t.element)}>`;
default:
return t.kind;
}
}
Loading
Loading