Skip to content

fate-bridge: run resolvers on one worker-level ManagedRuntime (F4 + F7)#24

Open
usirin wants to merge 6 commits into
mainfrom
umut/fate-bridge-fixed
Open

fate-bridge: run resolvers on one worker-level ManagedRuntime (F4 + F7)#24
usirin wants to merge 6 commits into
mainfrom
umut/fate-bridge-fixed

Conversation

@usirin
Copy link
Copy Markdown
Member

@usirin usirin commented Jun 1, 2026

Refactors the fate↔Effect bridge so every resolver runs on one worker-level ManagedRuntime (built once per isolate) instead of the old per-resolver Effect.runPromiseExit(Effect.provide(effect, capturedContext)) on the empty default runtime.

Why

The old bridge ran each resolver on a fresh root fiber on the default runtime, re-providing a captured services-only Context. That discards the runtime's tracer/scheduler/FiberRefs — so the ~59 Effect.fn spans were detached roots (F4). This runs resolvers through the worker runtime, so spans nest and the runtime's context is real.

What changed

  • FateContext carries { runtime, request, auth, liveBus } (a captured Context no longer). Generic in R (default WorkerFateServices) so tests can drive a tiny marker runtime.
  • runEffectctx.runtime.runPromiseExit(effect.pipe(provide Auth, provide LiveBus)). Per-request Auth/LiveBus are values provided onto each resolver effect, not baked into the runtime.
  • genEffect/runEffect generic in R; the single as survives as the irreducible F7 assertion (fate's Generator<any> resolver bodies make R unrecoverable) — kept + documented, no other casts.
  • Worker ManagedRuntime built once in index.ts init; makeAppLive/route.ts thread it by reference (never per request).

Proof

  • F4 win asserted in a unit test: a resolver's Effect.withSpan parents to the runtime's request span; the old empty-runtime path is a detached root.
  • 54/54 unit tests, pnpm typecheck green, biome clean, zero new as/as any in source.
  • Structural review applied (generic FateContext, stale-doc sweep, app.test.ts wiring, LiveEntities key, span typing).

This PR's preview deploy + integration suite are the live end-to-end gate (real POST /fate queries/mutations through the new bridge).

🤖 Generated with Claude Code

usirin and others added 6 commits June 1, 2026 01:58
…n, typecheck WIP

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alias, test wiring

Mechanical follow-ups to the ManagedRuntime bridge refactor:
- FateContext<R> generic (default WorkerFateServices); runEffect<A,R> runs each
  resolver THROUGH ctx.runtime, providing Auth/LiveBus per effect.
- WorkerRuntime type alias in layers.ts; used in route.ts/app.ts/bridge tests.
- effect.ts/test/bridge-* doc headers rewritten to the runtime-through model.
- app.test.ts mirrors prod wiring (ManagedRuntime.make + Layer.effectContext).
- effect.test.ts: LiveEntities key term->Definition, span typed Tracer.Span.
- genEffect keeps its single irreducible F7 assertion (documented).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marker-runtime isolation tests need a FateContext<R> the production-typed
bridge can run cast-free:
- genEffect<A,R> asserts the erased env to the caller's R (the single F7 cast),
  so runEffect<A,R> runs each resolver on ctx.runtime: ManagedRuntime<R>.
- SourceExecutor<R> + fateSource<Item, R = WorkerFateServices> parameterize the
  executor's ctx; production sources.ts keep the default (slot into the registry
  unchanged), the three fateSource isolation tests name Marker.
- effect.test.ts source tests call fateSource<..., Marker> so a marker ctx is
  accepted (fate's executor Context is invariant via SourcePlan).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e type) + format

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, Cause Option)

#1 Type resolver/source generator bodies with Effect.gen.Return<A, never, R>
   (pins only the env slot R) so Effect.gen infers R structurally — deletes the
   `Effect.gen(body) as Effect.Effect<A, unknown, R>` cast in genEffect and the
   per-wrapper R generic moves outward (default FateEnv / WorkerFateServices).
#2 Pass {signal: ctx.request.signal} to runPromiseExit so a disconnected fate
   client interrupts the resolver fiber (matches HttpEffect run-with-signal).
#5 Replace Cause.findError + `_tag === "Success"` with Cause.findErrorOption +
   Option.match (no Result tag leaks into boundary code). Rewrite the stale
   .patterns/fate-effect-bridge.md to the shipped ManagedRuntime model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Idiomatic review (#2,#5) landed; #1 (removing the genEffect cast via Effect.gen.Return)
reverted — pinning R in the generator yield is contravariant and cascades into fate's
QueryDefinition<FateContext<WorkerFateServices>> server constraint, so the single
contained boundary cast stays. runEffect now wires {signal: ctx.request.signal}
(client-abort interrupts the resolver fiber, matching HttpEffect) and unwinds the Cause
via Cause.findErrorOption + Option.match. .patterns/fate-effect-bridge.md updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant