You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Effect, Effect.Console and Test.Assert surfaces over the WASI library #95
Part of #76 (Phase 3). Remeasured 2026-10-03 on master at fe40d17: 258 passing files stop at Effect.Console, 43 at Effect, and 4 at Test.Assert — 305 files, by first blocking stage. No passing file blocks on Prelude, because stdlib/lib/Prelude.purs exists and resolves; its missing surface is latent rather than a first blocker, which is why #94 measures 0 by this convention.
We already have the runtime: WASI.Console wraps wasi:cli/stdout/stderr, and WASI-02/03 are Verified with execution tests. What is missing is the module surface the corpus expects.
Do
Effect and Effect.Console as a source-level surface over the existing WASI console, exporting log, logShow, warn, error and the Effect type the corpus imports.
Keep WASI.Console as the platform layer and have Effect.Console be a thin binding over it, so the WASI capability gates stay in one place.
Test.Assert over the same execution model: assert, assertEqual, assertThrows, assertFailure. A failing assertion must be observable as a non-zero exit, so passing assertion files score cleanly.
Note on DEC-11
docs/decision/DEC-11-primitive-ffi-stdlib-wrappers.md fixes the wrapper-versus-raw-import split. Effect.Console is a wrapper; the raw import stays inside WASI.Console. Do not collapse them.
Acceptance
Effect, Effect.Console, and Test.Assert exist, execute under Wasmtime, and the 305 blocked files drop toward zero.
Second slice landed in #127: the unit value and Test.Assert.
What landed
unit as a compiler primitive (Intrinsic::Unit). It lowers to Core ExprKind::Unit and to the canonical integer 0 only in CC, so no earlier stage reads it as an Int. unit and () reach the same value.
Test.Assert with assert, assert', assertTrue, assertFalse.
Prelude.trap, a psrs:effect external whose body is an unreachable path. A failed assertion writes its message to stderr and then traps; a guest trap, not a non-zero exit code, is the only failure signal a compilable corpus case can carry.
PSRS_REQUIRE_WASMTIME=1 cargo test -p psrs-driver --lib tests::assertions executes both paths (held assertion exits 0; failed one writes first, then traps, and nothing after the trap runs).
Measured (PSRS_ORACLE=annotations cargo test -p psrs-driver --test suite -- --ignored --nocapture)
Board
Before this slice
After
passing resolution
199/413
209/413
missing library module
83
62
P3 resolve
127
138
L1 / L3 / L4 / L5 / L6
904/908, 34/48, 34/50, 51/92, 0/413
unchanged
The 21 Test.Assert cases split into 10 that now resolve and 11 that stop on the next library gap; the unit half clears 11 unknown name unit blockers. The board's Corpus cases for this issue is now 18.
What remains on this issue
logShow (16 cases) and assertEqual (2 cases), both waiting on the Show/Eq class surface that #94 owns. assertThrows needs to observe a trap, which the target profile does not provide, so it is recorded as an obligation rather than approximated (D-04 M7, EF-12). The issue stays open until #94 lands that surface.
logShow :: forall a. Show a => a -> Effect Unit is now defined as log (show value) over the Show class from Data.Show. It adds no I/O and no
second stringifier: the rendering is the library's Show and the destination is
still the one WASI.Console.log decides. Verified under Wasmtime by tests::assertions::log_show_writes_the_library_rendering, which asserts the
renderings for Int, a negative Int, String, Char, Boolean, and Array Int.
Evidence
PSRS_REQUIRE_WASMTIME=1 PSRS_ORACLE=annotations cargo test -p psrs-driver --test suite -- --ignored --nocapture
at 071fb11 plus this slice, one run of all five boards:
logShow was the largest single P3 name blocker at 24 cases, 19 of them reported
as "logShow is not exported by the imported module". 17 resolve past L2; the
other 7 stop at the next P3 name, which nothing shadowed before — Monad and compare 2 each, and negate, not, and _ 1 each. The 17 land at later stages
as 10 P10, 4 P5 typecheck, 1 P8, and 2 P6.
Board Corpus cases for this issue is now 2.
assertEqual/assertEqual' are not landed, and the reason changed
Both classes are declared, so this is no longer a library-surface gap — it is a
compiler defect, filed as #137.
The corpus calls the record form (passing/DerivingFoldable.purs and one other
case):
A constraint whose quantified variable appears inside a record type is
elaborated with the record as the constraint's argument, so the wanted
constraint comes out as Eq { actual :: a, expected :: a } and the declaration is
rejected with NoInstanceFound. Probes that separate this from record syntax:
Signature
Result
forall a. Eq a => a -> Int
accepted
forall a. String -> { actual :: a, expected :: a } -> Int (no constraint)
accepted
forall a. { actual :: a, expected :: a } -> a — field access
accepted
forall a. { actual :: a, expected :: a } -> a — record pattern
accepted
forall a. Eq a => { actual :: a, expected :: a } -> Int
rejected
the same behind a type synonym for the record
rejected
A record whose fields are quantified variables is fine on its own, and both
destructuring forms work on it; only the constraint combined with the record
fails, and a type synonym does not change it, so this is constraint elaboration
rather than record syntax or the parser. Changing the argument shape would change
the API the corpus calls, so stdlib/lib/Test/Assert.purs records the blocker
instead of approximating it.
Still open on this issue
assertEqual and assertEqual' (2 passing cases), blocked on #137. assertThrows/assertThrows' remain a separate gap: they need to observe that an
argument trapped, which needs the Wasm exceptions proposal, outside DEC-05's
profile.
The 2026-10-17 runtime scoreboard no longer has a passing file whose first blocker is a missing Effect or Effect.Console surface. Corpus cases moves from 2 to 1.
The remaining file is passing/TCOMutRec.purs, which stops at P8 library linking because Test.Assert.checkThrows has no target implementation. A trap aborts the instance, so that foreign cannot return Boolean. assert, assertEqual, and the console logs do execute. This issue stays open for checkThrows.
Evidence: L6/M7 is 273/413 in docs/design/D-04-suite-roadmap.md, recorded by #145.
The library surfaces this issue owns exist and execute under Wasmtime, measured on master 93a5d31c (PRs #149–#155, the exception-channel adoption):
Effect and Effect.Console execute over the WASI console (long since landed); Test.Assert's assert/assertEqual/assertThrows all execute: checkThrows is implemented end to end over the adopted WebAssembly exception channel — the checked psrs:runtime-exception#checkThrows contract published (canonical CC signature, no host ABI), discharged at P9 into the inline catch_all try region, and verified through the real frontend: a guard around a genuinely non-tail rec at depth 100000 exhausts the measured stack budget (STACK_BUDGET=262144, weights 32+16×locals, pinned -W max-wasm-stack=524288, wasmtime 48.0.1) inside the guarded call and the catch delivers true, exit 0 (PSRS_REQUIRE_WASMTIME=1 driver evidence; stdlib 728a82f/fnv1a64-v1:5dfa6f2ce6b3092c, adaptation record docs/assert-throws.md).
The stable default profile enables the exception channel (DEC-23, amended 2026-10-19); L6/M7 re-measured 287/340 on 2026-10-19 (serial per-root, 10-second guest wall, release suite SHA-256 5d3bf058…): the failure-name set is identical to the 2026-10-18 baseline, and the only diff is TCOMutRec.purs's re-attribution from the P8 missing-foreign row to a recorded runtime-trap row — its four assertThrows assertions encode upstream's tail-call classification (the corpus's own comment says so) and are upstream-implementation-specific under the backend's mandated closure-tail reuse (DEC-23's amendment + the tail-call topic's boundary note). So Corpus cases = 0: no in-scope program is blocked on a missing Effect/Effect.Console/Test.Assert surface; the trap row is owned by the tail-call boundary, not a missing library module.
Activity
biuld commentedon Oct 3, 2026
Second slice landed in #127: the
unitvalue andTest.Assert.What landed
unitas a compiler primitive (Intrinsic::Unit). It lowers to CoreExprKind::Unitand to the canonical integer0only in CC, so no earlier stage reads it as anInt.unitand()reach the same value.Test.Assertwithassert,assert',assertTrue,assertFalse.Prelude.trap, apsrs:effectexternal whose body is an unreachable path. A failed assertion writes its message to stderr and then traps; a guest trap, not a non-zero exit code, is the only failure signal a compilable corpus case can carry.PSRS_REQUIRE_WASMTIME=1 cargo test -p psrs-driver --lib tests::assertionsexecutes both paths (held assertion exits 0; failed one writes first, then traps, and nothing after the trap runs).Measured (
PSRS_ORACLE=annotations cargo test -p psrs-driver --test suite -- --ignored --nocapture)passingresolutionThe 21
Test.Assertcases split into 10 that now resolve and 11 that stop on the next library gap; theunithalf clears 11unknown name unitblockers. The board'sCorpus casesfor this issue is now 18.What remains on this issue
logShow(16 cases) andassertEqual(2 cases), both waiting on theShow/Eqclass surface that #94 owns.assertThrowsneeds to observe a trap, which the target profile does not provide, so it is recorded as an obligation rather than approximated (D-04 M7, EF-12). The issue stays open until #94 lands that surface.biuld commentedon Oct 4, 2026
Effect.Console.logShowlanded — #138logShow :: forall a. Show a => a -> Effect Unitis now defined aslog (show value)over theShowclass fromData.Show. It adds no I/O and nosecond stringifier: the rendering is the library's
Showand the destination isstill the one
WASI.Console.logdecides. Verified under Wasmtime bytests::assertions::log_show_writes_the_library_rendering, which asserts therenderings for
Int, a negativeInt,String,Char,Boolean, andArray Int.Evidence
at
071fb11plus this slice, one run of all five boards:passingresolvedlogShowwas the largest single P3 name blocker at 24 cases, 19 of them reportedas "
logShowis not exported by the imported module". 17 resolve past L2; theother 7 stop at the next P3 name, which nothing shadowed before —
Monadandcompare2 each, andnegate,not, and_1 each. The 17 land at later stagesas 10 P10, 4 P5 typecheck, 1 P8, and 2 P6.
Board
Corpus casesfor this issue is now 2.assertEqual/assertEqual'are not landed, and the reason changedBoth classes are declared, so this is no longer a library-surface gap — it is a
compiler defect, filed as #137.
The corpus calls the record form (
passing/DerivingFoldable.pursand one othercase):
A constraint whose quantified variable appears inside a record type is
elaborated with the record as the constraint's argument, so the wanted
constraint comes out as
Eq { actual :: a, expected :: a }and the declaration isrejected with
NoInstanceFound. Probes that separate this from record syntax:forall a. Eq a => a -> Intforall a. String -> { actual :: a, expected :: a } -> Int(no constraint)forall a. { actual :: a, expected :: a } -> a— field accessforall a. { actual :: a, expected :: a } -> a— record patternforall a. Eq a => { actual :: a, expected :: a } -> IntA record whose fields are quantified variables is fine on its own, and both
destructuring forms work on it; only the constraint combined with the record
fails, and a type synonym does not change it, so this is constraint elaboration
rather than record syntax or the parser. Changing the argument shape would change
the API the corpus calls, so
stdlib/lib/Test/Assert.pursrecords the blockerinstead of approximating it.
Still open on this issue
assertEqualandassertEqual'(2passingcases), blocked on #137.assertThrows/assertThrows'remain a separate gap: they need to observe that anargument trapped, which needs the Wasm exceptions proposal, outside DEC-05's
profile.
StatusstaysIn Progress.Correct the first-stage blocker decomposition and close out Phase 2
Land Effect and Effect.Console, dropping 259 blocked files
Land the unit value and Test.Assert's assertion surface
Land Data.Show and the show method
biuld commentedon Oct 16, 2026
The 2026-10-17 runtime scoreboard no longer has a
passingfile whose first blocker is a missingEffectorEffect.Consolesurface.Corpus casesmoves from 2 to 1.The remaining file is
passing/TCOMutRec.purs, which stops at P8 library linking becauseTest.Assert.checkThrowshas no target implementation. A trap aborts the instance, so that foreign cannot returnBoolean.assert,assertEqual, and the console logs do execute. This issue stays open forcheckThrows.Evidence: L6/M7 is 273/413 in
docs/design/D-04-suite-roadmap.md, recorded by #145.biuld commentedon Oct 19, 2026
The library surfaces this issue owns exist and execute under Wasmtime, measured on master
93a5d31c(PRs #149–#155, the exception-channel adoption):EffectandEffect.Consoleexecute over the WASI console (long since landed);Test.Assert'sassert/assertEqual/assertThrowsall execute:checkThrowsis implemented end to end over the adopted WebAssembly exception channel — the checkedpsrs:runtime-exception#checkThrowscontract published (canonical CC signature, no host ABI), discharged at P9 into the inlinecatch_alltry region, and verified through the real frontend: a guard around a genuinely non-tailrecat depth 100000 exhausts the measured stack budget (STACK_BUDGET=262144, weights 32+16×locals, pinned-W max-wasm-stack=524288, wasmtime 48.0.1) inside the guarded call and the catch deliverstrue, exit 0 (PSRS_REQUIRE_WASMTIME=1driver evidence; stdlib728a82f/fnv1a64-v1:5dfa6f2ce6b3092c, adaptation recorddocs/assert-throws.md).5d3bf058…): the failure-name set is identical to the 2026-10-18 baseline, and the only diff isTCOMutRec.purs's re-attribution from the P8 missing-foreign row to a recorded runtime-trap row — its fourassertThrowsassertions encode upstream's tail-call classification (the corpus's own comment says so) and are upstream-implementation-specific under the backend's mandated closure-tail reuse (DEC-23's amendment + the tail-call topic's boundary note). SoCorpus cases= 0: no in-scope program is blocked on a missingEffect/Effect.Console/Test.Assertsurface; the trap row is owned by the tail-call boundary, not a missing library module.Effect.Exception,Effect.Aff(D-04's Effects row).