Conformance
perry-conformance tests Perry-WIT differentially against Node.js.
A contract names one capability, such as ecma.string.operations, and supplies directed cases plus a property-based generator (proptest) of TypeScript programs and inputs.
Executable cases and their metamorphic variants run as compiled components in a Wasmtime host with WASI 0.3, fuel metering and memory limits, and under Node.js, except aggregate cases with exact integers beyond 2^53, which use an expected-value model.
The runner checks that each variant matches its oracle and that transformations preserve return values, evaluation traces and standard streams. Rejection cases and their variants must report the required diagnostic.
Expected Outcomes
Each case declares what must happen:
| Outcome | Meaning |
|---|---|
EquivalentExecution | The component produces the same observable result as Node.js. |
CompilerRejection | The compiler rejects the program with a diagnostic, for constructs outside the subset. |
InjectedFaultDetection | The harness detects a deliberately broken export adapter: a wrong value, a dropped effect or a leaked host resource. These cases test the harness itself. |
BoundedGrowth | Fuel for an input of size 4n is at most 6 times the fuel for size n. This admits O(n log n) work and rejects quadratic growth (16 times). |
Expected-Pass List
passing.txt in perry-conformance lists the contracts that must pass.
A listed contract that fails is a regression, and an unlisted contract that passes fails the run until it is added, so the list only grows.
deferred.txt records contracts outside the supported domain with their exclusion rationale.
cargo run -p perry-conformance -- check
cargo run -p perry-conformance -- check --select node.fs
cargo run -p perry-conformance -- list
cargo run -p perry-conformance -- doc
--select filters by contract ID prefix. doc prints the catalog table below.
Catalog
The registry holds 63 capability contracts: 63 passing, 0 deferred, 0 pending.
ECMAScript
| Contract | Capability | Outcome | Partitions | Domain |
|---|---|---|---|---|
ecma.number.arithmetic | IEEE arithmetic and evaluation order | Equivalent execution | signed-zero, division, trace, call, record-field-update, byte-alias, checked-json, handled-throw | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.number.bitwise | Number bitwise operators, shifts and Math.imul | Equivalent execution | and, or, xor, not, left, right, unsigned-right, imul, trace, counts-and-compound-assignments | Number operands over the IEEE-754 boundary corpus; ToInt32/ToUint32 wrapping, non-finite inputs, masked shift counts, signed and unsigned results, evaluation order, compound assignments and loop-carried checksums; direct/local/branch/loop/loop-carried/inert-exception forms |
ecma.math.rounding | Numeric Math operations | Equivalent execution | floor, ceil, trunc, abs, round, abs-widened-loop | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.string.operations | String search, casing, slicing and length | Equivalent execution | unicode-and-nul, search, lower, upper, slice, char-at | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.array.dense | Dense numeric arrays preserve values and effects | Equivalent execution | index, branch-true, branch-false, first-index, array-effects | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.object.records | Record fields preserve values and effects | Equivalent execution | field-order | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.date.epoch | Date TimeClip and epoch reads | Equivalent execution | timeclip, nested-codecs | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.uint8array.conversion | Byte conversion and indexed access | Equivalent execution | wrapping, byte-input, byte-effects | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.json.roundtrip | JSON text survives direct and stored decoding | Equivalent execution | direct, stored, stored-unicode, direct-unicode | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
ecma.json.command | CLI JSON serialization and schema-guided parsing | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.command.arrays | CLI dense array manipulation and iteration | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.command.text | CLI UTF-8 string formatting, UTF-16 code units and regular expressions | Equivalent execution | standard-source | Standard CLI source; charCodeAt over ASCII/NUL, BMP and supplementary text with omitted, undefined and IEEE-754 boundary positions; exact output bytes and successful exit |
ecma.command.records | CLI closed record layouts and nested objects | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.command.numbers | CLI numeric formatting and boundary arithmetic | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.command.web | CLI Web API codecs and byte views | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.command.temporal | CLI ISO Temporal calendar and instant operations | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.promise.all | Concurrent Promise.all settlement ordering | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.date.clock | Platform wall-clock reads and ISO Date formatting | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
ecma.uint8array.set | Overlapping byte copies and atomic range rejection | Equivalent execution | empty, binary-subview | Uint8Array sources of 0..256 bytes; each source evaluated over every IEEE-754 boundary input; byte reads checked explicitly |
ecma.module.unawaited_initialization | Explicit compiler rejection of module initialization that entry points cannot await | Compiler rejection | unawaited-async-call | Synchronous top-level statements of a module with exported functions that start asynchronous work without awaiting it |
ecma.temporal.iso | Typed ISO Instant, PlainDate and PlainDateTime values | Equivalent execution | negative-millisecond, epoch, lower-limit, upper-limit | Integral epoch milliseconds in +/-8640000000000000, ISO formatting, leap-day addition and the ISO 8601 calendarId |
ecma.promise.all_settled | Fulfilled and rejected results retain their status and payload | Equivalent execution | zero, negative | Two numeric tasks; one fulfillment and one rejection for allSettled, two already-completed contenders for race; repeated component calls and IEEE-754 inputs |
ecma.promise.race | Already-settled contenders retain input order and repeated outcomes | Equivalent execution | zero, negative | Two numeric tasks; one fulfillment and one rejection for allSettled, two already-completed contenders for race; repeated component calls and IEEE-754 inputs |
ecma.error.values | Error messages, causes and identity across promise rejection and rethrow | Equivalent execution | empty-message, nul-and-unicode | TypeError with a string message and cause; repeated awaits, all/allSettled/race, rethrow and finally; hidden built-in fields; Unicode messages of 0..32 scalar values |
ecma.array.inferred_storage | Fresh empty arrays infer uniform or finite mixed elements from visible writes | Equivalent execution, Compiler rejection | uniform-records-and-aliases, dependent-arrays, mixed-index-writes, mixed-alias, mixed-branch, returned-alias, mixed-function-write, string-key-write, conflicting-conditional-field, conflicting-nested-array, unknown-call, closure-capture, reassigned-alias, stored-alias, mutable-element-alias, no-writes, conditional-temporary, number-writes, string-writes, boolean-writes, record-writes, nested-array-writes | Up to seven writes and four aliases over numbers, strings, booleans, records and nested arrays; annotations versus alias chains, branches, loops, temporaries, helper-return chains and indexed appends; three calls compare exact serialized contents and evaluation traces; finite mixed writes and returned arrays preserve values; unresolved writes remain rejected through temporaries and control flow; conservative escape boundaries |
ecma.object.inferred_storage | Fresh empty dictionaries share array inference for visible writes and aliases | Equivalent execution, Compiler rejection | computed-keys-and-json-residue, conflicting-alias, conflicting-branch, numeric-key, unknown-call, reassigned-alias, stored-alias, returned-alias, mutable-element-alias, open-json-source, number-writes, string-writes, boolean-writes, record-writes, nested-array-writes | Uniform string-keyed writes over numbers, strings, booleans, records and arrays; annotation versus alias, branch, loop and temporary forms; computed keys, assignment values, absent reads, reserved array property names, nested typed JSON residue; unknown sources and escaping mutations remain rejected |
ecma.aggregate.specialization | Orthogonal aggregate layout specialization across recursive shapes, exact 64-bit parseInt, origin scaling, and WIT boundaries | Equivalent execution | self-recursive-wide-parseint-and-float, nested-record-option-boundary-special-values-and-overflow | Product of flat/nested/self-recursive/mutually-recursive/array-bearing records, multi-field/list/option WIT exports, float/NaN/-0.0/int-arith/wide-parseInt value sources, branch joins, shared helpers, array concat/spread, and >32-origin summarization |
ecma.collection.iterators | Live Set and Map iterators across active, paused, and abandoned lifetimes under delete compaction and clear | Equivalent execution | set-paused-clear-foreach-and-reinsert, map-entries-compaction-and-abandoned-cursor-churn | Orthogonal state-machine sequences of NewIter, partial Step and ForEach with in-body mutations, Insert/InsertBatch, Delete/DeleteBatch across the 32-tombstone compaction threshold, Clear, and abandoned cursor churn over Set.values, Set.entries, Map.keys, Map.values, and Map.entries; Node is the oracle |
Web Platform
| Contract | Capability | Outcome | Partitions | Domain |
|---|---|---|---|---|
web.textencoder.subviews | UTF-8 encoding and byte subviews | Equivalent execution | subview-bounds | Bounded typed functions, control flow, closed aggregates, byte aliases, JSON conversion and exceptions over the IEEE-754 boundary corpus; BMP strings; direct/local/branch/loop/loop-carried/inert-exception forms and overlapping timer completion schedules |
web.console.routing | Standard output and error stream routing via console | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
web.performance.monotonic | Monotonic clock reads via performance.now | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
web.crypto.random | Cryptographic random byte fill via crypto.getRandomValues | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
web.streams.byob_reader | BYOB readers transfer shared buffers and fill visible views directly | Equivalent execution | empty, byte-boundary, transfer-boundary, beyond-guest-memory, cancel-and-reuse | Uint8Array destinations with offsets, alias detachment, buffer reuse, min:8192, short EOF, locking and cancellation; three calls per instance; exact output bytes within 64 KiB guest memory |
web.fetch.bounded_body | Headers-first Fetch, bounded byte consumption, completion failure and reuse | Equivalent execution | empty, binary-exact-limit, non-success-status, overflow-cancellation, completion-error | GET loopback HTTP; 200 and 404; binary bodies, explicit body cap and producer truncation; three calls per instance |
web.fetch.bounded_byob_body | BYOB bounded Fetch consumes directly into its final buffer | Equivalent execution | empty, binary-exact-limit, non-success-status, overflow-cancellation, completion-error | GET loopback HTTP; 200 and 404; binary bodies, explicit body cap and producer truncation; three calls per instance |
web.response.bytes | Constructed response snapshot, body use and repeated consumption | Equivalent execution | empty, binary-subview | Uint8Array sources of 0..256 bytes; each source evaluated over every IEEE-754 boundary input; byte reads checked explicitly |
web.request.bytes | Constructed request snapshot, body use and repeated consumption | Equivalent execution | empty, binary-subview | Uint8Array sources of 0..256 bytes; each source evaluated over every IEEE-754 boundary input; byte reads checked explicitly |
web.textdecoder.streaming | Fatal UTF-8 decoding across byte boundaries | Equivalent execution | empty, bom-and-split-scalar, invalid-sequence, incomplete-sequence | Fatal UTF-8, one-byte chunks, final flush; compare successful prefix bytes and normalized decoding failure |
web.headers.multimap | Header name folding, joined duplicates and mutations | Equivalent execution | empty-value, trim-and-duplicates | ASCII values, case-insensitive names, whitespace trimming, append/get/set/has/delete |
Node.js
| Contract | Capability | Outcome | Partitions | Domain |
|---|---|---|---|---|
node.timers.promises.value | Timer result | Equivalent execution | retained-operation | Retained async operation interleaved with typed numeric evaluation; instance reused across boundary inputs |
node.timers.promises.observers | Repeated timer observation | Equivalent execution | retained-operation | Retained async operation interleaved with typed numeric evaluation; instance reused across boundary inputs |
node.fs.readFile.utf8 | UTF-8 file read across retained and dynamic Unicode payloads | Equivalent execution | retained-operation, unicode-and-concurrent-read | Retained async UTF-8 read interleaved with numeric evaluation, plus dynamic Unicode/NUL payloads of 0..48 scalar values and concurrent reads |
node.fs.writeFile.text | Text file round trip, overwrite and truncation | Equivalent execution | retained-operation, overwrite-and-truncate | Retained JSON text round trip plus sequential UTF-8 writes testing file truncation and growth across arbitrary Unicode strings |
node.fs.metadata | File stat and directory listing across dynamic files and cleanup | Equivalent execution | retained-operation, multi-file-stat-and-unlink | Retained stat/readdir on fixture plus multi-file creation, exact byte-length stat verification, directory entry count and unlink cleanup |
node.fs.readFile.bytes | Binary file read, buffer isolation and UTF-8 decoding | Equivalent execution | retained-operation, binary-buffer-isolation | Retained binary read plus arbitrary 0..256-byte payloads, returned Uint8Array mutation isolation across repeated reads and TextDecoder verification |
node.fs.writeFile.subviews | Byte view and empty file round trip across arbitrary subarray slices | Equivalent execution | retained-operation, arbitrary-subview-slice | Retained subarray write plus arbitrary 0..128-byte buffers and subarray(start, end) slices, post-write view mutation isolation and empty subview truncation |
node.fs.readFile.recovery | Repeated rejection across missing UTF-8, binary and stat calls followed by recovery | Equivalent execution | retained-operation, multi-op-rejection-recovery | Retained rejected readFile plus repeated rejection observation across UTF-8 readFile, binary readFile and stat, followed by successful write/read recovery |
node.process.environment | Environment variable reads via process.env | Equivalent execution | standard-source | Standard CLI source; exact output bytes and successful exit |
node.timers.promises.lifecycle | Start, host release, completion, cancellation, observation and reuse | Equivalent execution | settlement-and-reobserve, cancel-and-reuse | Complete sequential lifecycle traces with controlled Node/WASI clocks; no claim of exhaustive async scheduling |
node.stream.writable_to_web | Queued acknowledged writes, writer locks and closed-writer rejection | Equivalent execution | empty, binary-and-close | stdout/stderr Uint8Array writes and subviews; two queued writes, repeat observation, close, rejected released-writer use and a new stdio wrapper; exact output bytes |
node.timers.callbacks.unsupported | Explicit compiler rejection of callback timers | Compiler rejection | callback-timer | Callback timers are outside the supported source domain |
node.fs.directories | Directory creation, listing, metadata and deletion | Equivalent execution | empty-file, binary-file | One directory and binary file; exact size, one listed entry, cleanup and reuse across the numeric boundary corpus |
node.process.exit_status | Explicit failure status and immediate process exit | Equivalent execution | exit-code, immediate-exit | Exit status 1 via process.exitCode or process.exit; exact preceding output and unreachable following output |
WASI 0.3 Host
| Contract | Capability | Outcome | Partitions | Domain |
|---|---|---|---|---|
wasi.execution.detect_value_fault | Injected faults must be distinguished from the unchanged program | Fault detection | injected-fault | Value corruption, omitted effects, and retained host resources; only the component adapter is faulted |
wasi.execution.detect_trace_fault | Injected faults must be distinguished from the unchanged program | Fault detection | injected-fault | Value corruption, omitted effects, and retained host resources; only the component adapter is faulted |
wasi.execution.detect_resource_fault | Injected faults must be distinguished from the unchanged program | Fault detection | injected-fault | Value corruption, omitted effects, and retained host resources; only the component adapter is faulted |
wasi.streams.incoming_bytes | Owned WIT byte streams use Web readers and acknowledged Web writes | Equivalent execution | empty, byte-boundary, transfer-boundary, beyond-guest-memory, cancel-and-reuse | Three calls per instance; input chunks of 8192 bytes; 64 KiB guest memory; exact output bytes, locking, cancellation and completion |
wasi.http.handler | Native HTTP ABI with WIT-generated request and response declarations | Equivalent execution | empty, binary-and-duplicates, request-response-limit | PUT requests with binary bodies up to 64 KiB, duplicate headers, statuses 200/201/404; three exchanges reuse one component instance |
wasi.execution.scaling.retained_records | Pushing n records into a local array and reading them back costs O(n) guest fuel | Bounded fuel growth | n-128 | Warm-up n, n, and 4n calls on one instance; n in 32..=128 generated, 128 directed; values equal Node, fuel ratio of the last two calls at most 6 |
wasi.execution.scaling.number_push | Pushing n numbers into a dense array and summing them costs O(n) guest fuel | Bounded fuel growth | n-128 | Warm-up n, n, and 4n calls on one instance; n in 32..=128 generated, 128 directed; values equal Node, fuel ratio of the last two calls at most 6 |
wasi.execution.scaling.temporary_records | One temporary record per loop iteration costs O(n) guest fuel in total | Bounded fuel growth | n-128 | Warm-up n, n, and 4n calls on one instance; n in 32..=128 generated, 128 directed; values equal Node, fuel ratio of the last two calls at most 6 |
wasi.execution.scaling.string_append | Appending one character n times costs at most n·log n guest fuel | Bounded fuel growth | n-128 | Warm-up n, n, and 4n calls on one instance; n in 32..=128 generated, 128 directed; values equal Node, fuel ratio of the last two calls at most 6 |
wasi.execution.scaling.collection_churn | Abandoned Set/Map iterators and insert-delete churn cost O(n) guest fuel | Bounded fuel growth | n-128 | Warm-up n, n, and 4n calls on one instance; n in 32..=128 generated, 128 directed; values equal Node, fuel ratio of the last two calls at most 6 |