For the complete documentation index, see llms.txt. This page is also available as Markdown.

Validation layers

Validation and operation policy layers

The word validation is often used broadly, but in the application there are several distinct layers. Each layer answers a different question and should be implemented in a different place.

The overall flow, from the origin request is:

1. API surface
2. Request DTO
3. Request validation
4. Service prerequisites
5. Operation policy
6. Database integrity
7. Return data DTO

A rule should be enforced at the earliest appropriate layer. Do not add an operation-policy blocker for an operation that should not exist in the API at all.


1. API surface

Question

Is this operation available at all?

Implementation

Typically implemented by:

and by the routes and HTTP methods exported by the module.

For example:

If control accounts are fixed system data, the API should not expose:

Creation and deletion are therefore not operations that need to be rejected by business logic. They are simply not capabilities provided by the API.

Principle

If an operation must never be performed, do not expose the API method.


2. Request DTO

Question

Which properties may be supplied or changed through the operation?

Implementation

Implemented in the request DTOs, typically in:

For example:

This DTO allows the linked GL account to be changed.

It does not allow the caller to change:

Those changes are therefore not supported by the PATCH operation.

The DTO defines the writable surface of the API. A property that is not present in the request DTO is not available for modification.

Principle

The API method defines the broad operation. The request DTO defines which changes are possible within that operation.


3. Request validation

Question

Is the supplied request correctly formed?

Implementation

Implemented in:

For example:

This layer validates the shape and basic values of the request:

Every DTO validator must define an exhaustive field-validator map. This is non-negotiable:

The mapped type ties the validator to the DTO at compile time. Adding, removing, or renaming a DTO property therefore requires the validator to be updated in the same change. The -? is required: optional DTO properties must still have an explicit validator that decides how undefined is handled.

Nested and cross-field rules may run after the field map, but they do not replace it. Validators that first narrow an unknown request must still declare the exhaustive typed map alongside their structural checks.

Typical failures produce:

This layer does not determine:

Those checks require application data and belong later in the flow.

Principle

validator.ts validates the request object, not the business circumstances surrounding the request.


4. Service prerequisites

Question

Can the service assemble the minimum complete information required to assess and perform the operation?

Implementation

Implemented in:

For example:

The service orchestrates the operation. Its responsibilities include:

For an UpdateGLAccount operation, the service must establish that:

Examples of service-prerequisite failures include:

These are not necessarily operation-policy blockers. They mean that the service cannot assemble the minimum information required for the policy to make a valid decision.

The operation policy is like a medical consultant: the service must provide a complete referral with the minimum required information.

Principle

The service gathers and verifies the information required by the policy; the policy should not query for missing information itself.


5. Operation policy

Question

Given complete information, is this supported operation permitted under the current business circumstances?

Implementation

Implemented in:

For example:

The operation-policy file contains pure functions such as:

Each meaningful operation should have its own function, even where two operations currently have identical blockers. Their rules may diverge later.

The functions:

An empty blocker array means the operation is valid.

For example, UpdateGLAccount may need:

It can then evaluate rules such as:

A typical result is:

The client can use blockers to disable or explain controls.

The server uses the same blockers to authoritatively reject the operation.

Principle

The policy evaluates business validity using complete inputs. It does not validate malformed requests or resolve missing records.


6. Database integrity

Question

Are structural invariants protected even if application logic fails or concurrent changes occur?

Implementation

Implemented in:

Examples include:

Not every business rule belongs in the database, but structural integrity should not depend solely on client or service behaviour.

Principle

Application policy expresses business rules; the database protects fundamental data integrity.


7. Return data DTO

Question

Does the data returned by the application conform to its response DTO?

Implementation

The repository returns a database row, and the module mapper converts that row into the response DTO. Before the service returns the DTO, it validates the mapped response using validateResponse from the entity's validator:

For example:

An invalid response indicates a defect or data-integrity problem rather than invalid caller input. Possible causes include:

In development, response validation must throw an error so the mismatch is found and corrected immediately. In production, it must log the validation error and return the response so a validation diagnostic does not itself cause an application outage.

Entity response validators should use the same exhaustive field-map pattern as request validation. Every property in the response DTO, including nested audit metadata, must be accounted for. All service paths that return the entity—including reads and the results of create, update, patch, and state transitions—must pass through the checked response.

Large composed report and posting DTOs must also be checked at the final service boundary. They may use withResponseValidation from @voyzu/capability/validation, which checks the complete returned object recursively for invalid DTO values and applies the same development and production policy. Their typed construction remains the compile-time contract; reusable entity DTOs nested within them should still use their entity's exhaustive validator when mapped.

Principle

Validate the mapped response DTO at the final service boundary: fail fast in development and report the mismatch without interrupting production.


Control-account example

Control accounts are fixed system data.

The layers therefore work as follows:

Creation

No creation policy is required.

Deletion

No delete policy is required.

Changing code, name, ledger, or status

These changes are not supported by the API.

Changing the linked GL account


Last updated