Why
Perry ships compatibility fixes reactively — users hit bugs, we patch. Recent open issues show the pattern: Effect mixins (#740), cross-module init at scale (#684), async_hooks / AsyncLocalStorage as name-only stubs (#788, #789), V8-fallback linker gaps (#678).
The root cause isn't any single bug — it's that we have no continuous, granular signal of where Perry stands on Node.js + TypeScript compatibility. CI reports a single 83% parity number; the gap-suite truncates output; test-parity/known_failures.json hides regressions; no Rust coverage tool runs.
This issue tracks the work to instrument compatibility, then close the holes the instrumentation reveals.
Scope — three axes of "compatibility"
- TypeScript language — closures, async, generators, classes, mixins, decorators, generics erasure, type-only imports, …
- JavaScript runtime semantics — Symbol, Proxy/Reflect,
new.target, prototype chain, JSON ordering, regex, async context propagation, …
- Node.js platform — built-in modules with real semantics (not just API surface), and the npm ecosystem on top.
A single percentage across all three is meaningless. We need per-axis, per-category trends.
Existing infrastructure (axis 3 — already committed)
Per @proggeramlug's comment, axis 3 is ~70% built and committed, not yet CI-wired:
test-files/test_parity_<module>.ts × 42 — byte-for-byte behavioral probes per node:* module, diffed against node --experimental-strip-types (commits d0e84724, 08c9004a, 1f2f44e5).
docs/runtime-parity.md — Node + Bun stdlib inventory (~50 modules, ~4,200 API rows).
docs/runtime-parity-gaps.md — that inventory diffed against Perry's real compile-time coverage; 2,417 true gaps quantified.
scripts/gen_parity_tests.py + scripts/parity-skiplist.toml — regeneration + documented out-of-scope list.
run_parity_tests.sh --filter parity_ — runs just the matrix, permissive-compile so gaps surface as runtime divergence.
Current baseline (v0.5.910 sweep): 5 PASS (os, tty, async_local_storage, argon2, ethers); path one fix from PASS (#810); 18 compile-fails on import * as X from "node:X" where Perry has no stdlib backing. Trend across 5 sweeps v0.5.713→v0.5.910: 0→5 PASS.
#812 tracks wiring this into CI with a per-module trend artifact. Sub-issues below compose with #812 rather than replace it: #812 = per-module diff of Perry's stdlib; #794 = per-category regression thresholds (seeded from #812's baseline); #800 = run Node's own test corpus.
Sub-issues
Structural blockers (cross-cutting, high-leverage)
Bugs that block many APIs at once belong on this roadmap rather than in per-API tickets:
Non-goals
- Implementing missing Node APIs (each gets its own issue) — except cross-cutting infra bugs that block many APIs at once, which are tracked above as structural blockers.
- Pushing the raw parity %. The point is to see it accurately; improvements follow from the signal.
Done when
All sub-issues above are closed, or explicitly deferred with rationale here.
Why
Perry ships compatibility fixes reactively — users hit bugs, we patch. Recent open issues show the pattern: Effect mixins (#740), cross-module init at scale (#684),
async_hooks/AsyncLocalStorageas name-only stubs (#788, #789), V8-fallback linker gaps (#678).The root cause isn't any single bug — it's that we have no continuous, granular signal of where Perry stands on Node.js + TypeScript compatibility. CI reports a single 83% parity number; the gap-suite truncates output;
test-parity/known_failures.jsonhides regressions; no Rust coverage tool runs.This issue tracks the work to instrument compatibility, then close the holes the instrumentation reveals.
Scope — three axes of "compatibility"
new.target, prototype chain, JSON ordering, regex, async context propagation, …A single percentage across all three is meaningless. We need per-axis, per-category trends.
Existing infrastructure (axis 3 — already committed)
Per @proggeramlug's comment, axis 3 is ~70% built and committed, not yet CI-wired:
test-files/test_parity_<module>.ts× 42 — byte-for-byte behavioral probes pernode:*module, diffed againstnode --experimental-strip-types(commitsd0e84724,08c9004a,1f2f44e5).docs/runtime-parity.md— Node + Bun stdlib inventory (~50 modules, ~4,200 API rows).docs/runtime-parity-gaps.md— that inventory diffed against Perry's real compile-time coverage; 2,417 true gaps quantified.scripts/gen_parity_tests.py+scripts/parity-skiplist.toml— regeneration + documented out-of-scope list.run_parity_tests.sh --filter parity_— runs just the matrix, permissive-compile so gaps surface as runtime divergence.Current baseline (v0.5.910 sweep): 5 PASS (
os,tty,async_local_storage,argon2,ethers);pathone fix from PASS (#810); 18 compile-fails onimport * as X from "node:X"where Perry has no stdlib backing. Trend across 5 sweeps v0.5.713→v0.5.910: 0→5 PASS.#812 tracks wiring this into CI with a per-module trend artifact. Sub-issues below compose with #812 rather than replace it: #812 = per-module diff of Perry's stdlib; #794 = per-category regression thresholds (seeded from #812's baseline); #800 = run Node's own test corpus.
Sub-issues
test-parity/threshold.json(seed from compat: wire the 42-module parity matrix into CI with per-module trend #812 baseline) — CI: per-category parity thresholds in test-parity/threshold.json #794cargo llvm-covweekly coverage report — CI: add cargo llvm-cov weekly coverage report #795known_failures.json— every entry needs issue # + date — test-parity: audit known_failures.json — every entry needs an issue # and date #797CLAUDE.mdparity status section — docs: refresh CLAUDE.md parity status section #798TypeError: value is not a functionduringParseResult.ts__init—class X extends TaggedError(...)<...>(Effect end-to-end blocker, post-#711) #740, perry-codegen: Effect — (number).slice is not a function during Schema.ts__init (#680 follow-up, ~310th init) #684) — compat: Effect framework end-to-end CI job #802ink(React-based TUI framework) end-to-end viaperry.compilePackages#348) — compat: ink (React TUI) end-to-end CI job #803TypeError: value is not a functionduringParseResult.ts__init—class X extends TaggedError(...)<...>(Effect end-to-end blocker, post-#711) #740) — tests: harness for dynamic class extension / mixins #806Structural blockers (cross-cutting, high-leverage)
Bugs that block many APIs at once belong on this roadmap rather than in per-API tickets:
node:*imports — recognized at compile time but not populated as accessible properties. Per @proggeramlug, this single bug is behind ~18 compile-fails plus the majority of near-pass diffs across ~20 modules in compat: wire the 42-module parity matrix into CI with per-module trend #812's sweeps. Follow-up to Namespace import for unrecognized node:* sub-paths resolves totrue(TAG_TRUE) #629 (whose v0.5.835 fix was the strict-import policy, not the population work). Tracking issue: TBD — see comment thread.Non-goals
Done when
All sub-issues above are closed, or explicitly deferred with rationale here.