effx
Introduction

The invariant

A decorator is a contribution to the IR, never generated behaviour. What that rules out, and the eleven invariants that follow from it.

decorator = contribution to IR, never generated behaviour.

Source: docs/decisions/0001-aot-compilation-is-authoritative.md, docs/decisions/0008-generated-code-is-ordinary-effect.md, docs/decisions/0012-positioning-enterprise-on-ramp.md, AGENTS.md.

What it means

A decorator or builder call says something about your code. The compiler records that claim as nodes and edges in the IR. Nothing the decorator does at runtime, and nothing the compiler generates, depends on the decorator being executed.

This is what lets effx be an on-ramp rather than a lock-in. The declaration is a familiar surface, and the generated Effect does not depend on it being executed. A team can keep the generated files and stop running the compiler (ADR 0012, Generated code).

@Http.Get("/users/:id")        source syntax
        │  lowers to
        ▼
Annotation { name: "Http.Get", args: ["/users/:id"] }
        │  interpreter returns
        ▼
Contribution { nodes: [Exposure], edges: [ExposedAs], diagnostics: [] }

Contribution is the type an interpreter returns. Its doc comment reads: "What one annotation on one declaration contributes to the IR. Never behaviour." (Source: packages/compiler/src/Extension.ts.)

What it rules out

Not allowedWhyADR
A decorator that wraps, replaces or registers the decorated method at runtimeRuntime decorators are no-ops or metadata only. The AOT compiler is the source of truth.0001
The compiler rewriting your handler bodiesThe compiler generates separate files and never rewrites bodies.0001
An effx dependency-injection container, runtime registry or reflectionGenerated code has no effx runtime. You provide ordinary Effect layers.0008
Needing to execute application modules to build the IRSchemas are kept as references. The frontend reads types with the TypeScript compiler API.0001, 0004
ts.* objects or source locations inside the IRThe IR holds semantics only. Locations go to the manifest.spec 0001, ADR 0002
Treating diagnostics as thrown failuresDiagnostics are data. CompilerFault is for IO and invariant breakage.spec 0001
An effx runtime that executes requests, or runtime reflection that changes behaviourGenerated code has no effx runtime.0008, 0012
Hiding E or R behind framework-style exceptions or ambient contextE and R are inferred from the handler and stay in types and Layers.0005, 0012
A generated-code format only the compiler can readOutput is ordinary Effect, deterministic and readable.0008, 0012

All invariants

The table below is the invariant table from AGENTS.md. ADR and spec pages are linked.

#InvariantReference
1AOT analysis is authoritative; runtime decorators are no-ops/metadata onlyADR 0001
2decorator = contribution to IR, never generated behaviourADR 0001
3IR is graph-shaped and defined with Effect Schema; Graph/Trie/HashMap are indexes, not the formatADR 0002
4Canonical JSON (RFC 8785 over the normalized IR) is a projection; semanticHash = sha-256 of itADR 0003
5Schemas are preserved as SchemaRef (module/export/symbolId); never serializedADR 0004
6E/R are inferred from the handler; @Errors/@Requirements assert and mismatches diagnoseADR 0005
7@Query is a semantic claim, not a purity proofADR 0006
8Cedar authorizes, effx issues leases (deferred past MVP)ADR 0007
9Generated code is ordinary Effect (HttpApi, Rpc, Command); no effx runtime DIADR 0008
10No ts.* objects in the IR. Source locations live in the manifest, not the IRspec 0001
11Diagnostics are data, not failures. CompilerFault is reserved for IO/invariant breakagespec 0001

Invariant 8 is a decision, not a feature. Cedar and leases are not built. STATE.md lists "Authority (Cedar, leases)" as deferred past the MVP.

How the code keeps it

RuleWhere it shows
Interpreters are pure: (annotation, declaration, ctx) => Contributionpackages/compiler/src/Extension.ts
Contributions merge and normalize before any analysis runsinterpret in packages/compiler/src/pipeline.ts
Analyses and generators only read the IR and its graph indexAnalysis, Generator in packages/compiler/src/Extension.ts
Output policy (target, emit mode) is a GenerationContext, never IRsee Emit modes and targets
Every Diagnostic is a plain value with a code, severity and locationsee Diagnostics

On this page