VIP-29: serializable errors: schema error
Status: accepted, 2026-10-29, under Will's standing approval of recommendations ("assume I accept now. I will interrupt you if I need to").
Decision
schema error NotFound { id: string }
// exactly Effect TS
class NotFound extends Schema.TaggedError<NotFound>()("NotFound", { id: Schema.String }) {}Encoding and decoding use Effect's Schema API (VIP-14). The encoded value follows the field codecs; encoding is not JSON serialization:
const encoded = try Schema.encodeEffect(NotFound)(err)
const back = try Schema.decodeUnknownEffect(NotFound)(encoded)JSON, worker messages and RPC transports must support that encoded representation. The declaration adds no implicit wire conversion. For example, Schema.BigInt encodes a bigint, which ordinary JSON cannot serialize; Schema.BigIntFromString encodes a string. Choose compatible codecs and a transport that preserves the required values and property keys.
import { Effect, Schema } from "effect"
class BigCount extends Schema.TaggedError<BigCount>()("BigCount", { count: Schema.BigInt }) {}
class JsonCount extends Schema.TaggedError<JsonCount>()("JsonCount", { count: Schema.BigIntFromString }) {}
const encodedBigCount = Schema.encodeSync(BigCount)(new BigCount({ count: 1n }))
const jsonTransportExit = Effect.runSyncExit(Effect.try({
try: () => JSON.stringify(encodedBigCount), catch: (error) => error,
})) // Failure(TypeError): count is still a bigint
const encodedJsonCount = Schema.encodeSync(JsonCount)(new JsonCount({ count: 1n }))
const jsonRoundTripExit = Schema.decodeUnknownExit(JsonCount)(JSON.parse(JSON.stringify(encodedJsonCount)))
// Success(JsonCount({ count: 1n })); the encoded count was "1"schema error Name { fields }combines VIP-13 (error) and VIP-15 (field mapping). Fields are written as TypeScript types and mapped to Schemas. Transport compatibility follows their encoded representations, as above.- The body rules are VIP-13's: methods and getters are allowed, and a constructor or a
_tagoverride is not. - Ambiguity: none.
schema error NotFound {and the alias formschema error = …differ at the next token. - Amended 2026-10-29: no braces.
schema error Timeout, with no body, is allowed, like VIP-13'serror Timeout. Sent to Will on Telegram 2026-10-29 11:28 under his standing rule 'assume I accept'; no objection by 12:00. - Amended 2026-10-29: the constructor's thrown type. A generated
schema errorconstructor is@throws {never}, a static contract likeerroranddataconstructors. A validation failure at construction (effect 4.1.1 throws a plainError) is a defect, as any implicit throw insideEffect.genis. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 42).
Amended 2026-10-30: referenced payload modifiers. Schema-error payload keys are readonly even when no readonly token is written. The referenced schema therefore needs readonly decoded-key mutability, and a required field needs required decoded-key optionality, under VIP-15's conservative reference admission. Conflicting or unprovable needed flags are rejected by those field-mapping rules; use an unmodified value schema. This checks effective field modifiers and keeps mutable nested values mutable.
Amended 2026-10-30: computed keys preserve the discriminant. A computed payload field key is rejected by the existing reserved-tag diagnostic unless its well-typed runtime property-key domain is proved to exclude "_tag". Effective generic constraints and enum underlying values matter; opaque primitive brands/refinements do not change a string's possible runtime keys. Retain genuine literal/template constraints in intersections and require every union component to exclude the reserved value. Known numeric/symbol domains, safe literal/template domains and their brands remain admitted; never has no key. Reject any, unknown and unresolved or unprovable domains conservatively. Accepted keys retain native evaluation/order, with no value unwrapping, decoder execution or runtime tag guard. Ordinary TypeScript invalid-key rules remain independent.
- Amended 2026-10-30: computed-key counterparts. Computed field keys use native runtime object-literal admission and evaluation, as for schema declarations. They must additionally prove their runtime key domain excludes
_tag; native key admission does not relax that reserved-tag check, add a runtime guard or imply transport compatibility. Plain error payloads retain their separate erased TypeScript type-member counterpart.