30. Risks and experiments
first, in the Go fork G19 one checker pass: candidate forms (D56), component fixpoint (D52)
the largest external risk G1 prompt capture uses undocumented Effect fields
the semantic gate G16 one law suite for both runtime strategies (R9 first)
the checker's foundation G8 second-class continuations and the finalizer ruleNo design question in this list is open: every row states the rule that stands, what could go wrong in building it, and the experiment that checks it. D57-D60 close the former open calls (Chapter 29); D57 and D58 are marked controversial. Upstream filing follows D59.
Risks, in the order to work on them
| # | Rule that stands | Risk | Experiment | What would change the rule |
|---|---|---|---|---|
| G19 | The call rule is decided by types (Chapter 13) in one checker pass: a lambda's form per overload candidate (D44, D56), E and R of a recursive component by a fixpoint inside it (D52) | The hypothesis survives on the corpus and the probes (prototype/single-pass/RESULTS.md): one patched check reaches the prototype's lowering in 46 of 46 files with 0 disagreements, and each probe disagreement is a lambda with two self-consistent lowerings, which D56 decides, or mutual recursion. What remains is the iteration inside a recursive component, which the harness lacks, and holding the three forms in the Go checker, where the harness duplicated each lambda's text | Build both in the Go fork (typescript/ in this repository, D60), compile the corpus and prototype/single-pass/probes/, and compare every lowering decision with the single-pass harness's and every contract with the prototype's | A construct that needs a check outside its own component; its rule changes so that one pass decides it |
| G20 | The prototype covers the language's core (Chapter 27) | It does not yet emit .d.ts files, $direct metadata, source maps, or hovers; implement EK4202 or external iteration of producers; or test this inside Effect methods | Extend the prototype in that order, then port its decisions to the Go fork | A part whose rule cannot be implemented as written |
| G1 | Strategy A is the runtime (Chapter 25) | An Effect release can break capture or abandonment without notice | Run the law suite against every Effect 4 patch release in CI. Prepare a supported primitive (an opaque same-fiber stack segment plus interruption state, or Effect.prompt) using the kernel as the reference; the proposal is drafted, not filed (D59) | A release breaks a law with no workaround, or the maintainers reject any splice API; capture-heavy code then falls back to strategy B and loses transparency through TypeScript combinators |
| G16 | Strategy B is the reference semantics | The two strategies disagree today (R9) and pass separate suites | Write the Chapter 20 laws once against a small interface with an adapter per strategy; run both. First law: the reinstalled prompt records the resume site's Context | A law the two cannot both express, which would mean Chapter 20 is ambiguous |
| G8 | Continuations are second-class; a finalizer handles its own control operations (Chapter 9) | The static checks are specified, not built | Implement both checks in a checker prototype; run them over the reference programs; count false positives | Correct handlers, such as schedulers, that need escapes transfer cannot express |
| G9 | Callback types without contracts are effect-polymorphic (D12); inferred contracts are shown on hover | Unreadable inferred types defeat inference | Infer the reference programs' contracts with the rules of Chapters 14 and 16; check multiple callbacks, returned callbacks, aliases, and declaration round trips | A callback that needs more than one row variable to read correctly |
| G10 | Plain JavaScript only where nothing observable changes (D10) | An optimization that changes behavior breaks settled decision 4 | Compile a corpus twice, all frames and optimized; compare results, event order, and deterministic interruption traces | Any observable difference |
| G2 | The fiber-behavior table is the diagnostic; the runtime is the guarantee (P10) | An unlisted combinator that forks costs a conservative error | Extend tests/fiber-behavior.test.ts to every Effect export that takes a program, and generate the checker's table from its results | An export whose fiber behavior depends on a run-time value the type cannot see |
| G3 | A continuation is consumed only on its capturing fiber (P5) | Closing an owning Scope from another fiber while the capturer is alive raises ContinuationWrongFiber at the closer; the segment is released only when the capturer exits (K13c) | A per-fiber pending-abandonment queue, drained when the capturing fiber next runs or exits; test producers shared across fibers | A common, correct pattern that needs cross-fiber consumption |
| G12 | A producer is a Stream to TypeScript; the adapter runs it on its own fiber (D26) | Stream cancellation might not reach the producer's owner | Adapt a producer to a pull Stream; pull from another fiber with Stream.merge; cancel early; check that the producer's finalizers ran once and that it never ran ahead | Stream cancellation that cannot reach the producer's fiber |
| G13 | wire reports missing, duplicate, and cyclic providers from Layer types (D27) | Layer types might not carry enough to order a graph | Implement wire over Layer types; test missing, ambiguous, cyclic, failing-acquisition, residual-requirement, and run-time-selected graphs | Layer types that do not carry enough to order the graph |
| G11 | The ABI and diagnostics map through .effeck spans (Chapters 27, 28) | The Go fork emits declarations, but its generated nodes carry no source positions and its language service is untested | In the fork, lower displayName; check the declaration, a type error at the .effeck span, and hover | Spans that lowering cannot map back to the .effeck source |
| G18 | Workflow bodies are ordinary programs under replay (D42) | A producer or handler inside a replayed body might keep state across executions | Run a workflow whose body iterates a producer and installs a handler, suspend it durably mid-iteration, replay it, and check that finalizers ran once per execution and nothing carried over | State that survives into a replay |
| G5 | Effect's own wake-ups are unbounded (F10), and its generator frames skip finally (F1-F3) | Effeck code waiting on Deferred chains inherits the overflow, and TypeScript users leak resources | The upstream index links minimal reproductions, expected behavior, and suggested fixes; every entry is drafted, not filed (D59) | Upstream treats unbounded synchronous wake-ups as intended; Effeck then wraps Deferred and PubSub waits in suspend |
| G14 | Abandonment unwinds as an interruption (P8) | Finalizers see a sentinel interrupt id; diagnostics cannot yet say "abandoned" | Annotate the abort cause, or add a reason, and check what Cause.pretty and finalizers show | |
| G15 | Typed failures do not cross JavaScript frames in v0 (D10, D9) | A pure helper that throws a typed error compiles to frames, not plain JavaScript | Bridge typed failures as branded exceptions caught at the nearest frame; measure cost and correctness | |
| G17 | Capture copies the segment (Chapter 25) | Capture at every level of a deep recursion copies quadratically (79 ms at depth 8,000) | Follow the segmented-stack draft: prototype opaque chunked segments in a patched Effect build and rerun bench/deep-capture.ts | Real producers nest deep enough for the quadratic term to dominate their own work |
Closed
| # | Question | Result |
|---|---|---|
| G4 | A handoff for producers that cannot act on a lost race | Built and tested (H1-H4, Chapter 21) |
| G6 | Spans and AsyncLocalStorage across prompt capture | Spans correct (G6a, T01). AsyncLocalStorage is not delimited (G6b), hence the rule in Chapter 22 |
| G7 | The cost of quadratic capture | Measured (Chapter 25); remaining work is G17 |
| W | Hand-lowering the reference programs to Chapter 26's shapes and running them on the kernel | Done by the prototype compiler, which lowers mechanically instead: its 46 example files, including the six reference programs (ref1 to ref6), compile and run on the kernel (prototype/esc) |
| Prototype | Thirty findings from compiling the corpus | Settled: D44-D53 |
| Single pass | Whether the call rule needs more than one checker pass | Not for lowering: one patched check per file reaches the prototype's output in 46 of 46 files (817 call sites, 257 lambdas), in 46 checks instead of 124. The probes' 16 lowering disagreements settled D56; mutual recursion amended D52; the Go work stays G19 (prototype/single-pass/RESULTS.md) |
| Review, first draft | Five kernel bugs from the conformance review (abandonment Exit, pending interruptions, abort leaking through a finalizer's capture, transfer revocation, registration leak) | Fixed; the review's 54 tests run with the suite |
| Reviews, 2026-10-16 | Five defects in implementation and lowering: tail resume from a compiled clause (R10, Fable 1), a cleanup defect dropped by explicit abandon (R2b), a transfer outliving its fiber (R5), Effect.catch recovering a defect (R1), and masked acquisition blocking cancellation (R3, Fable 2) | Fixed. Regression tests K11-K14 cover the kernel changes, and 12 of them fail on a copy of the kernel with the fixes reverted; K15 pins Effect's own acquisition behavior (experiments/KERNEL-CHANGES.md) |
| Reviews, 2026-10-16 | Rule gaps: catch with mixed causes, using ownership, resumeWith requirements, fork outside a region, provider sharing, control operations through TypeScript, function values in plain positions, trust for foreign code | Decided: D13, D14, D17, D19, D21, D20, D9, D5 with D22 and D23 |
| Coverage audit | Seventeen gaps found by mapping every Effect docs page (docs/research/effect-feature-coverage.md) | Decided: D22-D28 and D37-D43; tooling D30-D36; partial annotations are settled by D57 |
Reference programs
Programs the compiler must handle end to end. The prototype runs a version of each (prototype/esc/examples/ref1 to ref6):
- Existing Effect service. Import a generic, overloaded, aliased TypeScript Effect function; compose it without changing E or R; provide a Layer from TypeScript.
- Provider graph. A shared database, two dependent services, a service with a default, test overrides, a failing acquisition, a missing provider, an ambiguous provider, and a cycle. Acquisition dependencies must not leak to callers.
- Resourceful retry. Acquire, fail, release, retry with fresh locals, then cancel during a later attempt, including an interruptible acquisition. Each acquisition is released exactly once.
- Callbacks and foreign code. A pure
Array.map; a rejected callback that pauses; an effect-aware traversal; a trusted function with@throws; a callback API adapted withsuspendunder cancellation. - Programs as data. Store, return, retry, and concurrently compose a program without running it early; read a composed
Config; compare twoExitvalues. - Handlers. Nested prompts, one resumption, a rejected double resumption, abandonment with finalization, a producer with early
breakover 100,000 elements in constant stack, a producer consumed as aStreamfrom another fiber, and a control operation rejected at a fork.