Skip to content

Errors and Remedies

Blueprint errors are declaration vocabulary, not strings invented inside route handlers. A module owns a catalog of errors and custom remedies. Actions and rules point into that catalog with typed references. Runtime packages bind typed vyuh_errors parsers without putting functions into the portable Blueprint.

Identity contract

Every error definition has four distinct identities:

PropertyPurpose
schemaTypeSemantic discriminator: vyuh.blueprint.error.
nameStable declaration name within the module.
titleHuman-facing display string.
codeGlobally qualified wire and registry identity.

description is the safe default message. kind, severity, httpStatus, retryable, and recoverable drive protocol and UI behavior. Typed context fields describe the safe details allowed to cross the protocol boundary.

dart
const equipmentUnavailable = ErrorDefinition(
  name: 'equipment_unavailable',
  title: 'Equipment Unavailable',
  code: 'ops.equipment.unavailable',
  description: 'The equipment is temporarily unavailable.',
  kind: BlueprintErrorKind.conflict,
  httpStatus: 409,
  retryable: true,
  context: [
    ErrorContextField(
      name: 'equipment_id',
      title: 'Equipment',
      description: 'The equipment that rejected the operation.',
      valueType: ErrorContextValueType.identifier,
      required: true,
    ),
  ],
  remedies: [RemedyRef('vyuh.retry')],
);

Declare the definition on its owning module and reference it from actions and rules:

dart
Module(
  name: 'ops',
  title: 'Operations',
  schema: 'ops',
  version: '1.0.0',
  errors: [equipmentUnavailable],
  entities: [
    Entity(
      name: 'equipment',
      title: 'Equipment',
      tableName: 'equipment',
      facets: [
        IdentityFacet(
          actions: [
            Action(
              name: 'activate',
              title: 'Activate',
              raises: [ErrorRef('ops.equipment.unavailable')],
              rules: [
                Rule(
                  id: 'equipment_available',
                  kind: RuleKind.policy,
                  errorRef: ErrorRef('ops.equipment.unavailable'),
                ),
              ],
            ),
          ],
        ),
      ],
    ),
  ],
)

The validator rejects duplicate error codes, duplicate remedy IDs, unknown ErrorRefs, unknown custom RemedyRefs, duplicate context fields, and missing display identity.

vyuh_errors registry integration

At server bootstrap, BlueprintPlugin converts every ErrorDefinition into an ErrorCodeEntry and registers it with vyuh_errors. A BlueprintTarget.errorDescriptors adds domain parsers for typed StructuredException subclasses:

dart
BlueprintTarget(
  blueprint: opsBlueprint,
  errorDescriptors: [opsErrors],
)

This separation is intentional:

  • Blueprint owns portable identity, display text, status, context schema, and remedy references.
  • Runtime packages own Dart parser/factory functions.
  • Server and client register the same domain descriptor, so deserialization is symmetric.

Runtime exception families

StructuredException is the serialization and registry base. Blueprint uses two explicit runtime families above it:

  • BlueprintProtocolException owns Blueprint validation, compilation, protocol, storage-adapter, and runtime-integrity failures.
  • DomainException owns application and business failures declared by domain modules.

Concrete Blueprint exceptions must extend BlueprintProtocolException; concrete domain exceptions must extend DomainException. The client throws those exception families directly. HTTP and Dio remain adapter details; status, retryability, correlation IDs, and the safe response envelope are available separately as RemoteFailureMetadata. Callers therefore choose recovery and presentation by exception type rather than parsing a code prefix or depending on a transport-specific exception.

Protocol and stack-trace contract

The server catches (error, stackTrace) and logs the complete exception and stack on the server with a generated error_id. A caller receives the safe structured envelope:

json
{
  "error": {
    "code": "ops.equipment.unavailable",
    "kind": "conflict",
    "layer": "domain",
    "message": "The equipment is temporarily unavailable.",
    "status": 409,
    "error_id": "bp-...",
    "trace_id": "optional-request-trace",
    "retryable": true,
    "recoverable": true,
    "details": { "equipment_id": "eq-1" },
    "remedies": [{ "id": "vyuh.retry", "label": "Retry" }],
    "structured_error": {
      "code": "ops.equipment.unavailable",
      "context": { "equipment_id": "eq-1" }
    }
  }
}

Production responses never include raw stack traces, SQL text, credentials, or adapter internals. Operators correlate error_id or trace_id with server logs. The Blueprint HTTP client preserves the protocol envelope and dispatches structured_error through the registered vyuh_errors domain parser, so generic error UI can render typed details and remedies without matching messages.

Compatibility

Inline ActionError values remain readable for existing declarations. New declarations should use module ErrorDefinitions plus Action.raises and Rule.errorRef; this makes one catalog authoritative across docs, Studio, server status mapping, serialization, and client remedies.

Blue is the Vyuh Blueprint documentation surface.