# @systemfsoftware/oxlint-plugin-effect-schema

Oxlint rules for Effect Schema declarations — how a schema is declared and what a `*.schema.ts` may export.

## Rules

| Rule                                    | What it enforces                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ban-data-taggederror`                  | Use `Schema.TaggedError`, never `Data.TaggedError`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `no-manual-tag-member`                  | A type alias, interface, or type literal must not declare a `_tag` property signature; derive the type from `TaggedStruct` / `TaggedError` with `Schema.Type`, or inherit it from a tag carrier.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `no-manual-tag-property`                | A class must not declare its own `_tag`; use `TaggedClass` / `TaggedError`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `schema-declaration-location`           | A schema declaration (a class extending a Schema factory, or a module-scope const initialized to a `Schema.<member>(...)` call) must live in a `*.schema.ts` file or the `<stem>.workflow.ts` that owns it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `schema-file-exports-schemas-only`      | A `*.schema.ts` may export only its own schemas: schema declarations plus the type vocabulary (type aliases, interfaces, enums, type-only namespaces) the schemas are built from, the type-identity symbols (`Symbol.for('...')`) that name a type at runtime, and operations whose declared parameter or return type names one of those same-file types — read through a union or intersection member, a type predicate target, a type parameter's `extends` constraint or a `readonly T[]` element — declared on the function or in a const's declarator annotation (the `dual` shape). A const whose declarator annotation names one of those same-file types is data of that type and is admitted too. An exported function or call-initialized const with no explicit annotations, or one whose function return or const annotation names an `Effect`, `Stream` or `Layer` carrier, is banned — annotations name the type the operation serves, and a live computation belongs to a handle, service or workflow. An exported codec const (`S.encodeSync`, `S.decodeSync`, `Arbitrary.schema`, ...) and every re-export form are banned — the file declares its schemas, the caller applies them at the point of use. |
| `schema-file-imports-pure-modules-only` | A `*.schema.ts` may value-import only pure modules: Effect's pure data modules, its schema family, `effect/Effect` for fallible getters, the arbitrary module a `toCodecArbitrary` annotation needs, sibling `*.schema.ts` files and `@systemfsoftware/*` packages. Node builtins, other third-party packages and local non-schema modules are reported; type-only imports are free.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `schema-filter-constructive-generation` | A filter consumed by `.check()` — in member, destructured, or named-import form — or exported from a module carries constructive-generation metadata: `arbitraryConstraint` on scalar filters, an explicit `arbitrary: { residual: true }` declaration for dense struct-level invariants that generate fine by rejection sampling, or a move to `Schema.declare` with `toCodecArbitrary` for sparse invariants. Scalar chains with inert node-level overrides report `filterDiscards`; struct filters without metadata or the residual marker report `filterResidualUndeclared`. The override silencer fires only when the receiver terminates at `Schema.declare`. A filter without these generates by discarding with the real generator living outside the schema. Imported non-exported filters are trusted; exported filters must carry their metadata at home.                                                                                                                                                                                                                                                                                                                                                      |
| `schema-checked-element-named`          | A checked schema node passed inline as a collection combinator element must live in a named module-scope declaration, so the invariant has one home for its constructive metadata and its tests.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `schema-recursive-union-budget`         | A recursive union whose cycle carries six or more `Schema.suspend` members derives arbitrary values superlinearly — measured 8 ms at four members, 222 ms at six, 52.6 s at eight — so it must hoist to a single `Schema.suspend` at the recursion point and declare its ceiling with `recursionBudget`. A recursion point whose suspension returns a union that reaches itself and declares neither `recursionBudget` nor a `toCodecArbitrary` derivation is the same verdict one shape later. A cycle built by a call that does not resolve to the Schema vocabulary (any local builder) is a trusted builder. The verdict comes from one file's own value graph, so a cycle closed through an import, recursion carried by a `Schema.declare` type parameter, and suspends written inline in the member list all stay silent.                                                                                                                                                                                                                                                                                                                                                                                          |
| `tagged-error-requires-message`         | Every production class extending `Schema.TaggedError` must declare a message — a `message` getter on the class or a `message` field in its schema fields. Test files, `tests/`, `__tests__/` and `__fixtures__/` are exempt. A message-less class prints an empty failure headline, so the rule fails it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

## Enrollment

Turned on by `@systemfsoftware/oxlint-config/base`, which spreads `configs.recommended.rules`.

## Testing

Each rule ships a RuleTester suite at `src/rules/__tests__/<rule>.test.ts`, with 100% mutation coverage required.
