Coming from ASP.NET Core
A concept map from ASP.NET Core controllers, policies, ProblemDetails and OpenAPI to effx annotations and the Effect constructs underneath, a list of gaps, and one PATCH endpoint ported side by side.
effx bridges enterprise frameworks and full-stack Effect. You write a familiar declarative surface
(decorators or builder chains). The compiler reads it ahead of time and writes ordinary Effect that you can
read, edit or keep. This page maps ASP.NET Core concepts onto that surface and says, row by row, what exists
today (docs/decisions/0012-positioning-enterprise-on-ramp.md).
Pinned versions. ASP.NET Core claims cite learn.microsoft.com/aspnet/core
at ASP.NET Core 10.0 (view=aspnetcore-10.0). As fetched on 2026-10-04 the
.NET support policy lists .NET 10 as LTS in
the active phase and .NET 11 as a release candidate. Two pages are not published for 10.0: the NSwag tutorial
is cited at 8.0, and EF Core and Kiota live outside aspnet/core on Microsoft Learn. Effect claims cite the
official clone at https://github.com/Effect-TS/effect/tree/b5a2d4c1d62c9620a68d72b7f20248c69ef7663b (github.com/Effect-TS/effect, commit b5a2d4c, package effect
4.0.0). effx claims cite a spec or a source path in this repository.
Read this first: what an attribute means
| ASP.NET Core | effx | |
|---|---|---|
An attribute such as [HttpPatch] or [Authorize(Policy = "...")] | Metadata the framework reads at runtime: routing picks the action, and "the framework uses handlers to evaluate the requirements behind a policy" (Policy-based authorization). | A contribution to the IR. Runtime decorators are no-ops or metadata only, and the compiler (not a runtime) reads the source. Source: AGENTS.md invariants 1 and 2; ADR 0001. |
| Dependency injection | A built-in container: "ASP.NET Core provides a built-in service container, IServiceProvider" (Dependency injection). | No container. A handler's requirements R are inferred and @Requirements asserts them. You provide Layers. Source: ADR 0005; ADR 0012 ("Rules out" hidden DI). |
| Failures | Exceptions caught by exception-handling middleware or filters (Handle errors), plus MVC turning an error status code into a ProblemDetails body (web API controllers). | Expected failures are typed errors E in the handler's Effect<A, E, R>. @Errors asserts them and a mismatch is a diagnostic. @Http.Problems names the HTTP error responses. Source: ADR 0005; spec 0005. |
| Output | Behaviour lives in the framework at runtime. | Readable files in .effx/generated. A team can stop running the compiler and keep them. Source: ADR 0008; ADR 0012. |
Status vocabulary. supported: effx implements it (cited source or spec). via ecosystem package: an Effect package does it and effx composes with it; the path is cited. gap: nothing today (not called "planned" unless a spec says so). A cell can carry two statuses: first what effx itself provides, then what the Effect ecosystem provides.
Concept map
HTTP surface
| ASP.NET Core concept | effx annotation or construct | Underlying Effect construct | Status | Source |
|---|---|---|---|---|
Controllers, minimal APIs, routing: controllers, minimal APIs, routing, [HttpPatch] and verb templates | @Query or @Command plus @Http.Get/Post/Put/Patch/Delete(path), @Http.Group; builder twin Operation.command(...).http.patch(path). An operation class is a namespace of static methods, not an instantiated controller. | HttpApiEndpoint, HttpApiGroup, HttpApi, HttpApiBuilder.group | supported | Spec 0005; packages/runtime/src/{decorators,builder}.ts; packages/compiler/src/generate/http.ts; real output examples/users/.effx/generated/http.ts. See HTTP. |
Model binding and validation: model binding ([FromRoute], [FromBody]), model validation (DataAnnotations such as [Required] and [EmailAddress]) | input and success on @Query/@Command; params, query, headers, payload on @Http.Contract, each an Effect Schema. Rules are Schema checks, not attributes. | HttpApiEndpoint decodes each channel before the handler runs. A decode failure is HttpApiSchemaError, which "responds as an empty 400 Bad Request". | supported (decode); a problem body for decode failures: via ecosystem package (HttpApiMiddleware.layerSchemaErrorTransform) | examples/users/src/schemas.ts; spec 0005; packages/compiler/src/extensions/http-contract.ts; Effect packages/effect/src/http-api/HttpApiBuilder.ts:866-880, HttpApiError.ts:468-502, HttpApiMiddleware.ts:489. See Http.Contract. |
Dependency injection, service lifetimes: Dependency injection (AddSingleton, AddScoped, AddTransient). The options pattern is in the configuration row. | @Requirements(Service) asserts the handler's R. No module or container. | Context.Service and Layer. Layer construction is memoized and shared (MemoMap). A request-scoped value is a service that an HttpApiMiddleware provides. | supported (assertion, EFFX2302/EFFX2303); lifetimes: via ecosystem package (effect) | ADR 0005; ADR 0012; examples/users/src/{services,server-main}.ts; Effect packages/effect/src/Layer.ts:183, http-api/HttpApiMiddleware.ts:1-10,193. See Operations. |
Middleware pipeline: Middleware (Use, Run, Map; order is Program-file order) | Http.Contract.middleware: [Marker] per operation; Http.group({ defaults: { middleware } }) per group. Markers attach in declaration order and effx never instantiates one. | HttpApiMiddleware.Service plus .middleware(Marker) on the endpoint. App-wide: HttpMiddleware (logger, cors, tracer, xForwardedHeaders). | supported (attach marker); implementation and app-wide middleware: via ecosystem package (effect) | Spec 0005 (middleware row); packages/compiler/src/generate/http.ts:128-132; Effect http-api/HttpApiMiddleware.ts:67,340, http/HttpMiddleware.ts:149,199,299,341. See HTTP. |
| Filters: Filters (authorization, resource, action, exception, result) | No filter annotation. Authorization filter: Http.Access plus a security marker. Action filter: a middleware marker. Exception filter: @Errors plus the endpoint's error schemas. | One HttpApiMiddleware wraps the endpoint effect and receives the endpoint and group. Effect.catch* in a handler handles a failure locally. | via ecosystem package (effect); staged filter pipeline (resource, result): gap | packages/runtime/src/decorators.ts (full annotation vocabulary); spec 0005, 0006; Effect http-api/HttpApiMiddleware.ts:67-74. |
Security and errors
| ASP.NET Core concept | effx annotation or construct | Underlying Effect construct | Status | Source |
|---|---|---|---|---|
Authentication and authorization policies: policies, requirements, handlers, [Authorize], resource-based authorization | @Http.Access({ acceptedCredentials, principalKinds, capabilities, requirements, canonicalScopeResolver, concealment, decisionTime, annotator }) plus a security marker in Http.Contract.middleware. The handler receives a lazy authorize callback. @Authorize(capability) records capability facts only. | HttpApiMiddleware.Service with a security option (HttpApiSecurity.bearer, apiKey, basic); endpoint.annotateMerge(annotator(spec)); a guard (request) => Effect<Principal, Problem, R> that you write | supported (declaration, typed guard binding, EFFX2500-EFFX2504); evaluation: gap, application code; Cedar and leases deferred | Spec 0006; ADR 0007; packages/compiler/src/extensions/access-contract.ts; packages/compiler/src/generate/guards.ts; Effect http-api/HttpApiSecurity.ts:167,191,220. The Effect repository has no identity-provider package under packages/. See Problems and access. |
ProblemDetails and exception handling: Handle errors in APIs (AddProblemDetails, IProblemDetailsService, ProblemDetails), IExceptionHandler, RFC 9457 | @Http.Problems({ registry, codes, map }). registry is an exported function you write; effx keeps no code, status or body table. | Endpoint error option; HttpApiSchema.status / httpApiStatus for the status; HttpApiSchema.asJson({ contentType }) for the media type | supported (typed slot, EFFX2205/EFFX2206); problem body type, registry and domain-error conversion: application code. RFC 9457 types: gap | Spec 0005 §"Problem responses"; packages/compiler/src/extensions/problem-contract.ts; ai-docs/src/04_problems-and-access/; Effect http-api/HttpApiSchema.ts:131-146,946, test/httpapi/HttpApiClient.test.ts:1226-1238. No RFC 9457 module exists in packages/effect/src: the only match for problem+json there is the bundled Scalar UI asset http-api/internal/httpApiScalar.ts. |
The ASP.NET Core pages above describe their ProblemDetails type as based on RFC 7807
(web API controllers). RFC 9457 states
"This document obsoletes RFC 7807" (RFC 9457).
Contract outputs
| ASP.NET Core concept | effx annotation or construct | Underlying Effect construct | Status | Source |
|---|---|---|---|---|
| OpenAPI: Microsoft.AspNetCore.OpenApi | Http.Contract.metadata: operation ID, summary, description, tags and app annotator. Contract schemas supply errors and success. | OpenApi.annotations, annotateMerge, OpenApi.fromApi, HttpApiScalar.layer, HttpApiSwagger.layer | Annotations supported. The app owns its document and UI. | Spec 0005; packages/compiler/src/generate/http.ts; packages/frontend-ts/test/fixtures/stable-v4/src/profile-openapi.spec.ts |
| Client generation: NSwag (C# or TypeScript clients from the OpenAPI document; tutorial published up to 8.0), Kiota | Generated client.ts: a Client service and one typed function per operation. Also rpc.ts clients, an operation index, Foldkit commands. | HttpApiClient.ForApi, RpcClient, Context.Service | supported | Spec 0007; packages/compiler/src/generate/{client,rpc,foldkit}.ts; real output examples/users/.effx/generated/client.ts; Effect http-api/HttpApiClient.ts:71. See Generated code. |
Configuration and the options pattern: Configuration, Options pattern (IOptions<T>, ValidateDataAnnotations) | None. The composition root reads Config. | Config (Config.schema(Schema.Struct(...)) validates), ConfigProvider (environment, .env, JSON) | via ecosystem package (effect) | examples/users/src/server-main.ts (Config.Number("PORT").pipe(Config.withDefault(3000))); Effect packages/effect/CONFIG.md, src/Config.ts, src/ConfigProvider.ts:1200. |
Data and runtime
| ASP.NET Core concept | effx annotation or construct | Underlying Effect construct | Status | Source |
|---|---|---|---|---|
EF Core and migrations: EF Core, Migrations (dotnet ef migrations add) | @PersistentModel({ table, views, focus }) records Model and Focus nodes. It generates no SQL, no repository and no migration. | SqlClient, SqlSchema, SqlModel, Migrator in effect/sql; drivers such as @effect/sql-pg, @effect/sql-mssql, @effect/sql-sqlite-bun | gap (effx); hand-written data layer: via ecosystem package | STATE.md (persistence parked); spec 0008 (proposed, not frozen, codec-only wrappers, no migrations); packages/compiler/src/extensions/core.ts; examples/users/src/services.ts (in-memory Ref); Effect packages/effect/src/sql/{SqlClient,SqlSchema,SqlModel,Migrator}.ts, packages/sql/pg. See Persistence and authority. |
Background jobs: hosted services (IHostedService, BackgroundService) | No annotation. | A long-lived task is a Layer with Effect.forkScoped, run by Layer.launch. Schedule, Cron; durable: ClusterCron, PersistedQueue, Workflow. | effx declaration: gap; execution: via ecosystem package (effect) | packages/runtime/src/decorators.ts (no job annotation); examples/users/src/server-main.ts (Layer.launch); Effect Layer.ts:2209, Effect.ts:8666, Schedule.ts, Cron.ts, cluster/ClusterCron.ts, persistence/PersistedQueue.ts, workflow/Workflow.ts. |
Caching: IMemoryCache, output caching (AddOutputCache, UseOutputCache) | No annotation. For GET, Http.Contract.conditional adds a 304 beside the header-bearing success; the handler owns the ETag logic. | Cache, ScopedCache, PersistedCache, RateLimiter; ETag Generator in effect/http | in-process cache: via ecosystem package (effect); response/output caching middleware: gap | Spec 0005 (conditional); Effect packages/effect/src/{Cache,ScopedCache}.ts, persistence/{PersistedCache,RateLimiter}.ts, http/Etag.ts. |
| Realtime: SignalR | @Rpc(name) exposes an operation over the generated HTTP RPC endpoint (protocol: "http" at /rpc). No hub, no push, no streaming annotation. | RpcServer also supports a websocket protocol; Socket; SSE response schemas (HttpApiSchema.StreamSse) | effx declaration: gap; transport: via ecosystem package (effect) | examples/users/.effx/generated/http.ts (RpcServer.layerHttp); Effect rpc/RpcServer.ts:875, socket/Socket.ts, http-api/HttpApiSchema.ts:289. |
Tooling
| ASP.NET Core concept | effx annotation or construct | Underlying Effect construct | Status | Source |
|---|---|---|---|---|
Testing: integration tests with WebApplicationFactory | No harness. The example test starts a Bun test server and uses the generated clients. | HttpApiTest (in-memory generated client), RpcTest, BunHttpServer.layerTest, @effect/vitest | effx harness: gap; building blocks: via ecosystem package | examples/users/test/e2e.test.ts; Effect packages/effect/src/http-api/HttpApiTest.ts, rpc/RpcTest.ts, packages/platform/bun/src/BunHttpServer.ts:350, packages/vitest. |
CLI and scaffolding: dotnet new, aspnet-codegenerator | effx check, build, inspect, graph. @Cli("users get") makes the operation a command in generated cli.ts. | effect/cli Command | compile and generated CLI: supported; project templates and scaffolding generators: gap | scripts/effx.ts:25-88; packages/compiler/src/generate/cli.ts; CLI overview. The CLI is not published (see that page). |
Observability: logging with ILogger, OpenTelemetry, health checks (AddHealthChecks, MapHealthChecks) | None generated. | Logger, Effect.log, Effect.fn(name) spans, Metric; OtlpTracer, PrometheusMetrics, @effect/opentelemetry. A health check is a hand-written route, as in the HttpRouter.add("GET", "/health", ...) documentation example. | logging, tracing, metrics: via ecosystem package; health checks: gap | examples/users/src/services.ts (Effect.fn("Users.find")); Effect packages/effect/src/{Logger,Metric}.ts, observability/{OtlpTracer,PrometheusMetrics}.ts, Effect.ts:13665, http/HttpRouter.ts:479, packages/opentelemetry. No HTTP health-check module exists in packages/effect/src (cluster/RunnerHealth.ts checks cluster runners). |
Gaps today
These are not planned unless a spec says so. Each cites why it is a gap.
- Persistence, SQL and migrations. Spec 0008 is proposed, not frozen, and
STATE.mdlists persistence as parked.@PersistentModelrecords facts only. Useeffect/sqland a driver package by hand. - Authorization evaluation. effx records the policy and requires a typed guard. It never resolves a credential, interprets a relationship, chooses a principal or issues a lease (spec 0006). Cedar and leases are deferred (ADR 0007). Spec 0006 also states that a required binding "is not proof that the handler called it".
- Automatic problem responses. There is no
ProblemDetailstype, noAddProblemDetailsequivalent and no automatic 4xx mapping. The registry and the domain-error to problem conversion are application code until an explicit runtime mapping contract exists (spec 0005 §"Problem responses"). - Validation failure body. A request that fails decoding is an empty 400 by default. A
ValidationProblemDetailsstyle body needsHttpApiMiddleware.layerSchemaErrorTransformand your own error schema. - Jobs, queues, scheduling. No annotation exists (
packages/runtime/src/decorators.ts). The Effect modules in the table above are wired by hand. - Caching and output caching. No annotation and no middleware generated.
- Realtime. No hub-style annotation.
@Rpcis request and response over HTTP. - Scaffolding and generators. No
dotnet newtemplate and no code generator for controllers or models. The CLI has four commands. It is not published. - Testing harness. No generated or shipped fixture for
WebApplicationFactory-style tests. - Health checks. No Effect HTTP health-check module and no effx output. A
/healthroute is hand-written (packages/effect/src/http/HttpRouter.ts:479). - Staged filter pipeline. One
HttpApiMiddlewarewraps the whole endpoint. There are no resource or result stages. - Custom annotations. A compiler extension registered in
effx.config.tsgives a custom@Annotatename its meaning (Extensions, spec 0015). It adds IR and generated files; it does not add a runtime middleware or filter stage.
One endpoint, ported
PATCH /users/{id}/email:
- The caller needs a permission to change an email address (
[Authorize(Policy = "CanChangeEmail")]). - The body
{ "email": "..." }is validated. 404when the user does not exist and409when the email belongs to another user, both as problem responses.
Neither tab is compiled by this site. The C# is illustrative and each construct cites the Microsoft Learn page
that documents it. The effx tab shows the committed example files as they are, then a pasted extension. The
extension is assembled from patterns that compile in the repository
(ai-docs/src/04_problems-and-access/,
packages/frontend-ts/test/fixtures/users/src/{operations.access.ts,access-support.ts,contract-support.ts,operations.contract-status.ts}).
That exact combination was not built for this page.
Request model. Validation rules are DataAnnotations attributes on the model.
using System.ComponentModel.DataAnnotations;
public sealed class ChangeEmailRequest
{
[Required, EmailAddress]
public string Email { get; init; } = "";
}Source: Model validation
(built-in attributes in System.ComponentModel.DataAnnotations; [DataType] subclasses such as
[EmailAddress]).
Policy, requirement and handler. The requirement is a marker. The handler decides.
using Microsoft.AspNetCore.Authorization;
public sealed class ChangeEmailRequirement : IAuthorizationRequirement { }
public sealed class ChangeEmailHandler : AuthorizationHandler<ChangeEmailRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, ChangeEmailRequirement requirement)
{
if (context.User.HasClaim("permission", "users:change-email"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}builder.Services.AddControllers();
builder.Services.AddProblemDetails();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CanChangeEmail", policy => policy.Requirements.Add(new ChangeEmailRequirement()));
builder.Services.AddSingleton<IAuthorizationHandler, ChangeEmailHandler>();
// Authentication scheme registration (JWT bearer, cookies, ...) is omitted.Source: Policy-based authorization
(AddAuthorizationBuilder().AddPolicy(...), AuthorizationHandler<TRequirement>, context.Succeed,
"Handler registration" with AddSingleton<IAuthorizationHandler, ...>);
Dependency injection
(AddScoped); Handle errors in APIs
(AddProblemDetails).
Controller and service contract. With [ApiController], invalid model state returns an automatic 400 and
error status codes become ProblemDetails.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("users")]
public sealed class UsersController : ControllerBase
{
private readonly IUserService _users;
public UsersController(IUserService users) => _users = users;
[HttpPatch("{id}/email")]
[Authorize(Policy = "CanChangeEmail")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<IActionResult> ChangeEmail(string id, ChangeEmailRequest request)
{
switch (await _users.SetEmailAsync(id, request.Email))
{
case SetEmailResult.Updated updated:
return Ok(updated.User);
case SetEmailResult.NotFound:
return NotFound();
default:
return Problem(
statusCode: StatusCodes.Status409Conflict,
title: "Email already in use");
}
}
}
public interface IUserService
{
Task<SetEmailResult> SetEmailAsync(string id, string email);
}
public abstract record SetEmailResult
{
public sealed record Updated(UserSelf User) : SetEmailResult;
public sealed record NotFound : SetEmailResult;
public sealed record EmailTaken : SetEmailResult;
}
public sealed record UserSelf(string Id, string Email, string DisplayName);Source: web API controllers
([ApiController], "Automatic HTTP 400 responses", "Problem details for error status codes": NotFound()
"produces an HTTP 404 status code with a ProblemDetails body", [FromRoute] "inferred for any action parameter
name matching a parameter in the route template", [FromBody] inferred for complex types,
[ProducesResponseType(StatusCodes....)]);
controller routing
([HttpPatch]);
ControllerBase.Problem
(Problem(detail, instance, statusCode, title, type));
Simple authorization
([Authorize(Policy = "...")]).
ASP.NET Core code often throws instead and maps in an
IExceptionHandler.
The result type above keeps the two failures visible in the signature, which is the closer match to effx's E.
Construct by construct
| ASP.NET Core construct (above) | effx construct | Source |
|---|---|---|
[ApiController], [Route("users")], [HttpPatch("{id}/email")] | @Http.Patch("/users/:id/email"), @Http.Contract({ group: "users" }) | Spec 0005; examples/users/src/operations.ts |
string id bound from the route | params: ChangeEmailParams. Its fields must equal the route's :param names (EFFX2402 otherwise). | Spec 0005; Http.Contract |
ChangeEmailRequest with [Required, EmailAddress] | payload: ChangeEmailBody with Email as a Schema check | examples/users/src/schemas.ts; Effect Schema.ts:6656 (isPattern) |
Automatic 400 from [ApiController] | Decode before the handler; failure is an empty 400 by default | Effect HttpApiBuilder.ts:866-880, HttpApiError.ts:468-502 |
[Authorize(Policy = "CanChangeEmail")] | @Http.Access({ capabilities: Capability.one("User.ChangeEmail"), ... }) plus middleware: [UserSecurity] | Spec 0006; packages/compiler/src/extensions/access-contract.ts |
ChangeEmailRequirement and ChangeEmailHandler | Authz service and the guards record, called through authorize() | Spec 0006 §"HTTP lowering and guard interface" |
NotFound() returning ProblemDetails | UserNotFound failing the handler; the registry gives it 404 and application/problem+json | Spec 0005 §"Problem responses" |
Problem(statusCode: 409, ...) | EmailTaken failing the handler; 409 | Same |
[ProducesResponseType(...)] for 404 and 409 | The endpoint's error union. OpenApi.fromApi reads each error schema's status. | Effect OpenApi.ts:595-600 |
AddScoped<IUserService, UserService>() | Users.layer and @Requirements(Users, Authz) | examples/users/src/services.ts; ADR 0005 |
AddControllers() and the endpoint pipeline | AppRoutes(guards) composed with Layer.provide | examples/users/src/server-main.ts |
What the port preserves, and what it does not
Preserved (by construction or by cited source):
- The verb, the route and the single operation name.
- Request validation before application code runs. The handler cannot see an invalid
email. - A 404 and a 409 for the two domain failures, each as
application/problem+jsonwith the RFC 9457 memberstype,titleandstatus. - Both failures appear in the handler's type (
E), in@Errors, in the endpoint'serroroption, in the OpenAPI document and in the generated client's error type. ASP.NET Core lists them in[ProducesResponseType]by hand. - The access declaration is data in the IR (
AccessContract, spec 0006). It is not code that the framework runs.
Not preserved:
- Access evaluation stays application code.
[Authorize(Policy = "...")]runs in the framework before the action. In effx,Http.Accessrecords the policy and the generated handler passes a lazyauthorize()thunk. If the handler never calls it, nothing denies the request. Spec 0006: "A required typed binding is not proof that the handler called it"; the real gate is an end-to-end run of your own journey. - No
traceIdand noClientErrorMapping. For controllers, ASP.NET Core turns every error status code (400 and above) into aProblemDetailsbody by default (web API controllers). effx returns a problem body only for the codes inHttp.Problems.codes. A status without a code is not a problem body. - Problem responses follow spec 0005. The registry, the body shape and the domain-error conversion are yours.
The compiler diagnoses an unmapped error (
EFFX2205) and a mapping to an absent code (EFFX2206) and nothing else. - Validation failures are empty 400s. ASP.NET Core returns
ValidationProblemDetailsfor invalid model state. Effect returns an empty 400 unless you addHttpApiMiddleware.layerSchemaErrorTransform. - RFC 7807 versus 9457. The ASP.NET Core pages describe 7807. This port uses the 9457 media type and members.
- The email rule differs. The pattern is the application's own, not the
[EmailAddress]attribute. - Storage differs.
examples/usersuses an in-memoryRef<HashMap>, not EF Core; writes do not survive a restart (STATE.md, "Deferred / notes"). The uniqueness check is inUsers.setEmail, not a database constraint. - The 401 challenge is yours. ASP.NET Core's authentication middleware issues it. In effx the
UserSecurityLayer you write decodes the credential and fails withAccessDenied. - Not compiled here. Nothing on this page was built or run for this version of the page. The C# is illustrative and the TypeScript extension is assembled from compiled fixtures.
Next
Coming from NestJS
A concept map from NestJS controllers, pipes, guards and filters to effx annotations and the Effect constructs underneath, a list of gaps, and one endpoint ported side by side.
Coming from Laravel
Laravel 13.x concepts mapped to effx annotations and the Effect constructs underneath, with the gaps stated and one endpoint ported side by side.