diff --git a/.agents/skills/writer/SKILL.md b/.agents/skills/writer/SKILL.md
new file mode 100644
index 0000000000..478d25398c
--- /dev/null
+++ b/.agents/skills/writer/SKILL.md
@@ -0,0 +1,47 @@
+---
+name: writer
+description: Write, edit, and restructure user-facing and developer-facing documentation for the Spine `validation` repository. Use when asked to create/update docs such as `README.md`, `docs/**`, `CONTRIBUTING.md`, and other Markdown documentation; when drafting tutorials, guides, troubleshooting pages, or migration notes; and when improving inline API documentation (KDoc) and examples.
+---
+
+# Write documentation (repo-specific)
+
+## Decide the target and audience
+
+- Identify the target reader: end user, contributor, maintainer, or tooling/automation.
+- Identify the task type: new doc, update, restructure, or documentation audit.
+- Identify the acceptance criteria: “what is correct when the reader is done?”
+
+## Choose where the content should live
+
+- Prefer updating an existing doc over creating a new one.
+- Place content in the most discoverable location:
+ - `README.md`: project entry point and “what is this?”.
+ - `docs/`: longer-form docs (follow existing conventions in that tree).
+ - `CONTRIBUTING.md`: contributor workflows.
+ - Source KDoc: API usage, examples, and semantics that belong with the code.
+
+## Follow local documentation conventions
+
+- Follow `.agents/documentation-guidelines.md` and `.agents/documentation-tasks.md`.
+- Use fenced code blocks for commands and examples; format file/dir names as code.
+- Avoid widows, runts, orphans, and rivers by reflowing paragraphs when needed.
+
+## Make docs actionable
+
+- Prefer steps the reader can execute (commands + expected outcome).
+- Prefer concrete examples over abstract descriptions.
+- Include prerequisites (versions, OS, environment) when they are easy to miss.
+- Use consistent terminology (match code identifiers and existing docs).
+
+## KDoc-specific guidance
+
+- For public/internal APIs, include at least one example snippet demonstrating common usage.
+- When converting from Javadoc/inline comments to KDoc:
+ - Remove HTML like `
` and preserve meaning.
+ - Prefer short paragraphs and blank lines over HTML formatting.
+
+## Validate changes
+
+- For code changes, follow `.agents/running-builds.md`.
+- For documentation-only changes in Kotlin/Java sources, prefer `./gradlew dokka`.
+
diff --git a/.agents/skills/writer/agents/openai.yaml b/.agents/skills/writer/agents/openai.yaml
new file mode 100644
index 0000000000..44eaa4e241
--- /dev/null
+++ b/.agents/skills/writer/agents/openai.yaml
@@ -0,0 +1,5 @@
+interface:
+ display_name: "Writer"
+ short_description: "Write and update user/developer docs"
+ default_prompt: "Write or revise documentation in this repository (for example: README.md, docs/**, CONTRIBUTING.md, and API documentation/KDoc). Follow local documentation guidelines in .agents/*.md, keep changes concise and actionable, and include concrete examples and commands where appropriate."
+
diff --git a/.agents/skills/writer/assets/templates/doc-page.md b/.agents/skills/writer/assets/templates/doc-page.md
new file mode 100644
index 0000000000..f405b71e15
--- /dev/null
+++ b/.agents/skills/writer/assets/templates/doc-page.md
@@ -0,0 +1,23 @@
+# Title
+
+## Goal
+
+State what the reader will accomplish.
+
+## Prerequisites
+
+- List versions/tools the reader needs.
+
+## Steps
+
+1. Do the first thing.
+2. Do the next thing.
+
+## Verify
+
+Show how the reader can confirm success.
+
+## Troubleshooting
+
+- Common failure: likely cause → fix.
+
diff --git a/.agents/skills/writer/assets/templates/kdoc-example.md b/.agents/skills/writer/assets/templates/kdoc-example.md
new file mode 100644
index 0000000000..fdbd9b6a0d
--- /dev/null
+++ b/.agents/skills/writer/assets/templates/kdoc-example.md
@@ -0,0 +1,11 @@
+````kotlin
+/**
+ * Explain what this API does in one sentence.
+ *
+ * ## Example
+ * ```kotlin
+ * // Show the typical usage pattern.
+ * val result = doThing()
+ * ```
+ */
+````
diff --git a/.agents/skills/writer/assets/templates/kotlin-java-example.md b/.agents/skills/writer/assets/templates/kotlin-java-example.md
new file mode 100644
index 0000000000..5517516f56
--- /dev/null
+++ b/.agents/skills/writer/assets/templates/kotlin-java-example.md
@@ -0,0 +1,13 @@
+{{< code-tabs langs="Kotlin, Java">}}
+
+{{< code-tab lang="Kotlin" >}}
+```kotlin
+```
+{{< /code-tab >}}
+
+{{< code-tab lang="Java" >}}
+```java
+```
+{{< /code-tab >}}
+
+{{< /code-tabs >}}
diff --git a/.agents/tasks/archive/concepts-plan.md b/.agents/tasks/archive/concepts-plan.md
new file mode 100644
index 0000000000..2185a4137c
--- /dev/null
+++ b/.agents/tasks/archive/concepts-plan.md
@@ -0,0 +1,57 @@
+**Goal**: explain how Validation works (one layer deeper than “getting started”)
+- Add a Concepts landing page and an “options overview” page that explains:
+ where options come from, how they’re applied, and what code gets generated.
+- Keep this conceptual (no option-by-option details yet).
+- Target outcome: a reader can explain that Validation is enforced by generated code
+ (not by parsing descriptor/options at runtime) and that the Gradle plugin wires the
+ Validation Compiler into the build to augment `protoc` output.
+- Terminology: use “Validation Compiler” consistently (it is a plugin to the Spine Compiler).
+- Pages to create/update:
+ - Concepts landing: `docs/content/docs/validation/02-concepts/_index.md`
+ - Options overview: `docs/content/docs/validation/02-concepts/options-overview.md`
+- Concepts landing page: planned content (conceptual, 1 screen deeper than “Getting started”)
+ - Open with a 2–3 sentence “mental model”:
+ - Constraints are declared in `.proto` files using built-in options imported from `spine/options.proto`.
+ - The Validation Compiler updates Java sources generated by `protoc` so runtime checks happen in code.
+ - Explain the “build-time vs runtime” boundary:
+ - Build-time: options are read from descriptors and used to generate/inject checks into message code.
+ - Runtime: there is no descriptor parsing; validation runs as regular code in `build()` / `validate()`.
+ - Add a small pipeline diagram (Mermaid) showing:
+ `.proto` + `spine/options.proto` import → `protoc` (Java) → Validation Compiler (via Gradle plugin)
+ → generated Java API (`build()`, `validate()`).
+ - Define (briefly) what Protobuf custom options are with a one-sentence refresher,
+ then link to Protobuf docs for details.
+ - Clarify “what you write” vs “what you get”:
+ - You write: option annotations on fields/messages.
+ - You get: runtime enforcement through `build()` throwing `ValidationException` and
+ `validate()` returning an error object (as introduced in “Getting started”).
+ - Mention extensibility without diving in:
+ - Note that custom validation options + codegen are possible; link forward to
+ `docs/content/docs/validation/08-custom-validation/_index.md`.
+ - Close with “What’s next” links:
+ - Options overview page.
+ - Custom validation (for organization-specific rules).
+ - Developers guide (architecture/key modules) for internals.
+- Options overview page: planned content (concrete examples, but not an option catalog)
+ - Purpose statement: explain where built-in options come from and how they influence generated code.
+ - Where options come from (built-ins only):
+ - Options are defined in `spine/options.proto` (and related Spine option protos).
+ - Users import the option proto(s) and annotate their own message/field definitions.
+ - One-sentence refresher on Protobuf options + a link to Protobuf docs about custom options.
+ - How options are applied at build time:
+ - The Gradle plugin integrates Validation into the build.
+ - Validation Compiler uses the compiled descriptors (including option values) to augment `protoc` output.
+ - What code gets generated (high-level, user-facing):
+ - `build()` performs validation and throws `ValidationException` on violations.
+ - `validate()` can be used to obtain a structured validation error without throwing.
+ - (Avoid helper-class details; keep terminology aligned with “Using the generated code”.)
+ - What does *not* happen:
+ - Validation does not parse descriptor option data at runtime to decide what to validate.
+ - 1–3 tiny illustrative `.proto` snippets (no deep semantics):
+ - Example: `(required)` on a field.
+ - Example: `(pattern)` on a string field.
+ - Example: `(min)/(max)` on a numeric field.
+ Each snippet should be used only to anchor the “options → generated code” explanation.
+ - Add “What’s next” links:
+ - Built-in options reference (future section 4).
+ - Custom validation (for defining your own options/rules).
diff --git a/.agents/tasks/archive/error-messages-plan.md b/.agents/tasks/archive/error-messages-plan.md
new file mode 100644
index 0000000000..78921306b9
--- /dev/null
+++ b/.agents/tasks/archive/error-messages-plan.md
@@ -0,0 +1,34 @@
+# Task: Write documentation on working with validation error messages
+- Placement of the page: Under the Concepts section after the “Options overview” page.
+- Purpose: explain how validation error messages work, how to customize them, and how to use them in code.
+- Target outcome: a reader can explain the relationship between validation options and error messages,
+- how to define custom messages, and how to format them for end users or diagnostics.
+- Terminology: `TemplateString`, `ValidationError`, `ConstraintViolation`, `format()`, `formatUnsafe()`
+- Describe that validation options have default error messages and user-defined error messages
+ (via the `error_msg` field of a validation option, such as `(pattern).error_msg`).
+- Give an example of a custom validation error message with placeholders
+ defined in a proto field option. Use `spine/options.proto` as a reference for
+ the option definition and the `error_msg` field.
+- Explain the notion of placeholders in error messages and how they correspond to
+ values provided at runtime when a violation occurs.
+- Describe how `TemplateString` works (placeholders + values) and how to convert it to a
+ human-readable message (formatting).
+- Clarify the recommended ways to work with Validation errors in:
+ - Kotlin: `TemplateString.format()` / `TemplateString.formatUnsafe()`.
+ - Java: `io.spine.validation.TemplateStrings.format(TemplateString)`
+ - Java: `io.spine.validation.TemplateStrings.formatUnsafe(TemplateString)`.
+- Explain the structure of `ValidationError` / `ConstraintViolation`, and what fields developers
+ should use when:
+ - displaying messages to end users;
+ - logging diagnostics (e.g. include `type_name`, `field_path`, and the unformatted template).
+- Add troubleshooting notes for common runtime formatting problems (e.g. missing placeholder
+ values; choosing `formatUnsafe()` when partial substitution is acceptable).
+- Source references to anchor the docs:
+ - `jvm-runtime/src/main/proto/spine/validation/error_message.proto`
+ - `jvm-runtime/src/main/proto/spine/validation/validation_error.proto`
+ - `jvm-runtime/src/main/kotlin/io/spine/validation/TemplateStringExts.kt`
+ - `jvm-runtime/src/main/kotlin/io/spine/validation/RuntimeErrorPlaceholder.kt`
+
+## Output
+
+Implemented as `docs/content/docs/validation/02-concepts/error-messages.md`.
diff --git a/.agents/tasks/built-in-options-plan.md b/.agents/tasks/built-in-options-plan.md
new file mode 100644
index 0000000000..90df03f04d
--- /dev/null
+++ b/.agents/tasks/built-in-options-plan.md
@@ -0,0 +1,7 @@
+# Task: Publish a minimal reference set on built-in validation options
+- Placement: a separate section coming after the "Concepts" section.
+- From `docs/_options/options.proto`,
+ enumerate the built-in options and group them (fields, strings, numbers, collections, message-level).
+- For each documented option: purpose, supported field types, common pitfalls, and a short `.proto` example.
+- Start with the options already used in docs/examples: `(required)`, `(pattern)`, `(min)/(max)`,
+ `(distinct)`, `(validate)`.
diff --git a/.agents/tasks/custom-validation-plan.md b/.agents/tasks/custom-validation-plan.md
new file mode 100644
index 0000000000..831e9c6115
--- /dev/null
+++ b/.agents/tasks/custom-validation-plan.md
@@ -0,0 +1,47 @@
+# Task: Expand “Custom validation” docs (custom options + codegen)
+
+## Goal
+
+Turn the existing “Custom validation” landing page into a complete, end-to-end guide for
+implementing organization-specific rules via custom Protobuf options and code generation.
+
+Target outcome: a reader can define a custom option, register it, implement reaction/view/generator,
+and verify it works in a consumer project.
+
+## Placement
+
+- Placement of the pages: `docs/content/docs/validation/08-custom-validation/`.
+- Keep the current landing page as an overview and add a single practical walkthrough page.
+
+## Planned content
+
+- Clarify the extension surface:
+ - Custom Protobuf option definition (`extend google.protobuf.*Options`).
+ - Option discovery + validation (reaction).
+ - Accumulating a model (views, plus policies if needed).
+ - Generating/injecting Java validation code (generator).
+- Make the steps actionable:
+ - Show file/Gradle locations where each piece belongs in a consumer project.
+ - Explain registration points:
+ - `io.spine.option.OptionsProvider`
+ - `io.spine.tools.validation.java.ValidationOption` (SPI for custom option implementations)
+- Provide a minimal “walkthrough” with the existing `(currency)` sample:
+ - Point to the option declaration, the reaction/view/generator, and the registration.
+ - Explain the intended contract: what rule is enforced, where the error message comes from.
+- Add a short troubleshooting section:
+ - Option not discovered (missing `OptionsProvider`).
+ - Generator not invoked (missing `ValidationOption` SPI entry).
+ - Illegal option application should fail compilation (where to look for error messages).
+
+## Source references to anchor the docs
+
+- Existing overview page:
+ - `docs/content/docs/validation/08-custom-validation/_index.md`
+- Full working example:
+ - `:tests:extensions` module (custom `(currency)` option implementation)
+
+## Output
+
+- Update: `docs/content/docs/validation/08-custom-validation/_index.md`.
+- Add: `docs/content/docs/validation/08-custom-validation/currency-example.md`.
+
diff --git a/.agents/tasks/developers-guide-plan.md b/.agents/tasks/developers-guide-plan.md
new file mode 100644
index 0000000000..7d584d77e2
--- /dev/null
+++ b/.agents/tasks/developers-guide-plan.md
@@ -0,0 +1,51 @@
+# Task: Complete Developer’s guide (architecture and key modules)
+
+## Goal
+
+Make the “Developer’s guide” pages sufficient for maintainers and contributors:
+the reader should understand the compilation pipeline, where each responsibility lives,
+and where to start when debugging a doc/codegen/runtime issue.
+
+## Placement
+
+- Placement of the pages: `docs/content/docs/validation/09-developers-guide/`.
+- Expand the existing pages without adding many new sections (keep it minimal).
+
+## Planned content
+
+- Architecture page (`architecture.md`)
+ - Add a legend / step-by-step explanation for the existing diagram:
+ - where options are discovered;
+ - where the validation model is built (policies/views);
+ - where Java code is generated/injected;
+ - what artifacts flow between stages (descriptors, generated sources, resources).
+ - Call out the main extension points and where they plug in:
+ - custom options (reaction/view/generator + SPI);
+ - external message validators (`MessageValidator` + `@Validator` + KSP discovery).
+ - Add “Where to look” links:
+ - built-in options reference;
+ - custom validation section;
+ - key modules page.
+
+- Key modules page (`key-modules.md`)
+ - Keep the tables, but add a 1–2 paragraph “debugging map”:
+ - build-time problems (compiler/plugin) vs runtime problems (generated code / runtime library).
+ - Add a short list of “common entry points” by scenario:
+ - option semantics or compilation errors → `:context`, `:java`, `:gradle-plugin`;
+ - validator discovery problems → `:ksp`, `:java`;
+ - error message formatting → `:jvm-runtime`.
+
+## Source references to anchor the docs
+
+- Existing pages:
+ - `docs/content/docs/validation/09-developers-guide/architecture.md`
+ - `docs/content/docs/validation/09-developers-guide/key-modules.md`
+- External message validator mechanism (for cross-linking):
+ - `jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt`
+ - `jvm-runtime/src/main/kotlin/io/spine/validation/Validator.kt`
+
+## Output
+
+- Update: `docs/content/docs/validation/09-developers-guide/architecture.md`.
+- Update: `docs/content/docs/validation/09-developers-guide/key-modules.md`.
+
diff --git a/.agents/tasks/third-party-messages-plan.md b/.agents/tasks/third-party-messages-plan.md
new file mode 100644
index 0000000000..690a7f7ec9
--- /dev/null
+++ b/.agents/tasks/third-party-messages-plan.md
@@ -0,0 +1,60 @@
+# Task: Document validating third-party (external) messages
+
+## Goal
+
+Explain how to enforce validation rules for Protobuf message classes that are **already generated**
+by third parties and therefore cannot be updated via `.proto` option annotations + codegen.
+
+Target outcome: a reader can pick the right strategy depending on whether they control the `.proto`
+source, and can implement an external message validator that is automatically applied when
+validating local messages.
+
+## Placement
+
+- Placement of the page: after “Built-in options”, before “Custom validation”.
+- Hugo section (minimal change): add the page under `docs/content/docs/validation/02-concepts/`.
+ If the site navigation later gains an “Advanced topics” section, the page can move there.
+
+## Planned content
+
+- Define “local” vs “external” messages:
+ - Local: `.proto` sources compiled in the current build; Validation injects checks into generated code.
+ - External: message classes already compiled (e.g., come from dependencies); codegen cannot be applied.
+- What does and does not work:
+ - You cannot attach Validation options to fields of external messages unless you rebuild their `.proto`.
+ - External validators are applied **only when an external message is a field inside a local message**.
+ - External validators are not applied transitively inside other external messages.
+- Recommended strategy decision tree:
+ - If you control `.proto`: prefer built-in options or custom validation options (codegen).
+ - If you don’t control `.proto`: use `MessageValidator` + `@Validator`.
+- How external validation works (high-level):
+ - Implement `io.spine.validation.MessageValidator`.
+ - Annotate the implementation with `@io.spine.validation.Validator(M::class)`.
+ - Ensure a `public` no-args constructor (required by discovery/instantiation).
+ - Validation invokes the validator for:
+ - singular fields of type `M`;
+ - repeated fields of type `M`;
+ - map values of type `M`.
+- Error reporting shape:
+ - Return `List`.
+ - Use `FieldViolation` (and other available violation types) to point at a field path and value.
+ - Mention that the runtime converts `DetectedViolation` into `ConstraintViolation`/`ValidationError`.
+- Constraints and guardrails:
+ - Exactly one validator per external message type (duplicate is an error).
+ - Validators for local messages are prohibited (use options/codegen instead).
+- Example walkthrough (short, copy-pastable):
+ - Implement `EarphonesValidator` (from `:tests:validator`) and show how it affects a local message
+ that contains an `Earphones` field.
+
+## Source references to anchor the docs
+
+- External validation API and requirements:
+ - `jvm-runtime/src/main/kotlin/io/spine/validation/MessageValidator.kt`
+ - `jvm-runtime/src/main/kotlin/io/spine/validation/Validator.kt`
+- Example implementation:
+ - `tests/validator/src/main/kotlin/io/spine/tools/validation/test/EarphonesValidator.kt`
+
+## Output
+
+Implemented as `docs/content/docs/validation/02-concepts/third-party-messages.md`.
+
diff --git a/.agents/tasks/validation-documentation-plan.md b/.agents/tasks/validation-documentation-plan.md
index fb45460d54..8152490abd 100644
--- a/.agents/tasks/validation-documentation-plan.md
+++ b/.agents/tasks/validation-documentation-plan.md
@@ -6,12 +6,11 @@ buildable documentation set, without expanding scope unnecessarily.
## Key locations (source of truth)
-- Docs content (Hugo): `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/`
+- Docs content (Hugo): `docs/content/docs/validation/`
- Protobuf options reference (for built-ins):
- - `/Users/sanders/Projects/Spine/validation/docs/_options/options.proto`
- - `/Users/sanders/Projects/Spine/validation/docs/_options/time_options.proto`
-- Example projects for embedded snippets: `/Users/sanders/Projects/Spine/validation/docs/_examples/`
-- Docs build notes: `/Users/sanders/Projects/Spine/validation/docs/GRADLE.md`
+ - `docs/_options/options.proto`
+- Example projects for embedded snippets: `docs/_examples/`
+- Docs build notes: `docs/GRADLE.md`
## Definition of done (for a “first complete docs cut”)
@@ -25,51 +24,44 @@ buildable documentation set, without expanding scope unnecessarily.
1) Information architecture (IA): make Hugo navigation coherent
- Status: DONE (2026-02-23).
- Added/updated section landing pages:
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/_index.md`
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/09-developers-guide/_index.md`
+ - `docs/content/docs/validation/_index.md`
+ - `docs/content/docs/validation/09-developers-guide/_index.md`
- Replaced broken `.../index.md` links with directory links where appropriate:
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/00-intro/_index.md`
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/01-getting-started/_index.md`
+ - `docs/content/docs/validation/00-intro/_index.md`
+ - `docs/content/docs/validation/01-getting-started/_index.md`
- Added “What’s next” navigation to keep a clear reading path:
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/00-intro/target-audience.md`
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/00-intro/philosophy.md`
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/09-developers-guide/architecture.md`
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/09-developers-guide/key-modules.md`
- - `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/01-getting-started/first-model.md`
+ - `docs/content/docs/validation/00-intro/target-audience.md`
+ - `docs/content/docs/validation/00-intro/philosophy.md`
+ - `docs/content/docs/validation/09-developers-guide/architecture.md`
+ - `docs/content/docs/validation/09-developers-guide/key-modules.md`
+ - `docs/content/docs/validation/01-getting-started/first-model.md`
- Fixed an obvious broken image reference:
- `/Users/sanders/Projects/Spine/validation/docs/content/docs/validation/08-custom-validation/_index.md`
+ `docs/content/docs/validation/08-custom-validation/_index.md`
2) Complete “Getting started” flow
- Status: DONE (2026-02-24).
- Validate that the "Getting started" section covers:
importing options, build-time validation, `build()` vs `buildPartial()`, and `validate()`.
-3) Concepts: explain how Validation works (one layer deeper than “getting started”)
-- Add a Concepts landing page and an “options overview” page that explains:
- where options come from, how they’re applied, and what code gets generated.
-- Keep this conceptual (no option-by-option details yet).
-
-4) Built-in options: publish a minimal reference set
-- From `/Users/sanders/Projects/Spine/validation/docs/_options/options.proto` and
- `/Users/sanders/Projects/Spine/validation/docs/_options/time_options.proto`,
- enumerate the built-in options and group them (fields, strings, numbers, collections, message-level, time).
-- For each documented option: purpose, supported field types, common pitfalls, and a short `.proto` example.
-- Start with the options already used in docs/examples: `(required)`, `(pattern)`, `(min)/(max)`,
- `(distinct)`, `(validate)`, `(when)`.
-
-5) Runtime API usage (Java + Kotlin)
-- Document the two primary usage patterns:
- - fail-fast on `build()` (throws `ValidationException`);
- - non-throwing `validate()` (returns `Optional`).
-- Link to the runtime entry points used by generated code:
- `/Users/sanders/Projects/Spine/validation/jvm-runtime/src/main/java/io/spine/validation/ValidatableMessage.java`,
- `/Users/sanders/Projects/Spine/validation/jvm-runtime/src/main/java/io/spine/validation/ValidatingBuilder.java`,
- `/Users/sanders/Projects/Spine/validation/jvm-runtime/src/main/java/io/spine/validation/Validate.java`,
- `/Users/sanders/Projects/Spine/validation/jvm-runtime/src/main/java/io/spine/validation/ValidationException.java`,
- `/Users/sanders/Projects/Spine/validation/jvm-runtime/src/main/kotlin/io/spine/validation/MessageExtensions.kt`.
-
-6) Verification pass (keep it tight; fix only doc-related issues)
-- From `/Users/sanders/Projects/Spine/validation/docs/`, run:
+3) [Concepts](archive/concepts-plan.md)
+- Status: DONE (2026-02-26).
+
+4) [Working with error messages](archive/error-messages-plan.md)
+- Status: DONE (2026-02-27).
+
+5) [Built-in options](built-in-options-plan.md): publish a minimal reference set
+
+6) [Validating third-party messages](third-party-messages-plan.md)
+
+7) [Custom validation: defining your own options and codegen](custom-validation-plan.md)
+
+8) [Developer's guide: architecture and key modules](developers-guide-plan.md)
+
+## Verification pass
+
+Keep it tight; fix only doc-related issues.
+
+- From `docs/`, run:
- `./gradlew embedCode`
- `./gradlew checkSamples`
- `./gradlew buildSite`
diff --git a/dependencies.md b/dependencies.md
index bf23d06224..7ce35d6304 100644
--- a/dependencies.md
+++ b/dependencies.md
@@ -1,6 +1,6 @@
-# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -1139,14 +1139,14 @@
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -1731,7 +1731,7 @@ This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:02 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
@@ -1752,7 +1752,7 @@ This report was generated on **Mon Feb 23 18:35:33 WET 2026** using
-# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -2841,14 +2841,14 @@ This report was generated on **Mon Feb 23 18:35:33 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -3935,14 +3935,14 @@ This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.auto.service. **Name** : auto-service-annotations. **Version** : 1.1.1.
@@ -4005,14 +4005,14 @@ This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:01 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine:validation-jvm-runtime:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine:validation-jvm-runtime:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -4845,14 +4845,14 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-ksp:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-ksp:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.auto.service. **Name** : auto-service-annotations. **Version** : 1.1.1.
@@ -5781,14 +5781,14 @@ This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -6379,14 +6379,14 @@ This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:01 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -6897,14 +6897,14 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -7588,14 +7588,14 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -8217,14 +8217,14 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -8889,14 +8889,14 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:03 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -9647,14 +9647,14 @@ This report was generated on **Tue Feb 24 20:44:06 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:02 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -9924,14 +9924,14 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:02 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.398`
+# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.399`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -10282,6 +10282,6 @@ This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Tue Feb 24 20:44:05 WET 2026** using
+This report was generated on **Thu Feb 26 22:11:01 WET 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
\ No newline at end of file
diff --git a/docs/_examples b/docs/_examples
index c15b15240a..4d3727541f 160000
--- a/docs/_examples
+++ b/docs/_examples
@@ -1 +1 @@
-Subproject commit c15b15240a2d784dae161f84d316f9cf435843fc
+Subproject commit 4d3727541f0c4f771a6c1a215eef065cd3e74032
diff --git a/docs/_options/time_options.proto b/docs/_options/time_options.proto
deleted file mode 100644
index bc73fe15f5..0000000000
--- a/docs/_options/time_options.proto
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2025, TeamDev. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-syntax = "proto3";
-
-// API Note on Packaging
-// ---------------------
-// We do not define the package for this file to allow shorter options for user-defined types.
-// This allows to write:
-//
-// [(when).in = FUTURE];
-//
-// instead of:
-//
-// [(spine.time.when).in = FUTURE];
-//
-
-import "spine/options.proto";
-
-option (type_url_prefix) = "type.spine.io";
-option java_package = "io.spine.time.validation";
-option java_outer_classname = "TimeOptionsProto";
-option java_multiple_files = true;
-
-import "google/protobuf/descriptor.proto";
-
-extend google.protobuf.FieldOptions {
-
- // See `TimeOption`.
- TimeOption when = 73819;
-}
-
-// Specifies that the field value is a point in time lying either in the future or in the past.
-//
-// Applicable to `google.protobuf.Timestamp` and types introduced in the `spine.time` package
-// that describe time-related concepts.
-//
-// Repeated fields are supported.
-//
-// Example: Using the `(when)` option.
-//
-// message ScheduleMeeting {
-// spine.time.ZonedDateTime start = 1 [(when).in = FUTURE];
-// }
-//
-message TimeOption {
-
- // The default error message.
- option (default_message) = "The field `${parent.type}.${field.path}`"
- " of the type `${field.type}` must be in the `${when.in}`."
- " The encountered value: `${field.value}`.";
-
- // Defines a restriction for the timestamp.
- Time in = 1;
-
- // Deprecated: please use `error_msg` instead.
- string msg_format = 2 [deprecated = true];
-
- // A user-defined error message.
- //
- // The specified message may include the following placeholders:
- //
- // 1. `${field.path}` – the field path.
- // 2. `${field.value}` - the field value.
- // 3. `${field.type}` – the fully qualified name of the field type.
- // 4. `${parent.type}` – the fully qualified name of the validated message.
- // 5. `${when.in}` – the specified timestamp restriction. It is either "past" or "future".
- //
- string error_msg = 3;
-}
-
-// This enumeration defines restriction for date/time values.
-enum Time {
-
- // The default value (if the time option is not set).
- TIME_UNDEFINED = 0;
-
- // The value must be in the past.
- PAST = 1;
-
- // The value must be in the future.
- FUTURE = 2;
-}
diff --git a/docs/_preview/go.mod b/docs/_preview/go.mod
index ced82e07df..3d83edfef3 100644
--- a/docs/_preview/go.mod
+++ b/docs/_preview/go.mod
@@ -3,7 +3,7 @@ module spine.io/validation/docs/preview
go 1.25.6
require (
- github.com/SpineEventEngine/site-commons v0.0.0-20260225164144-d5e941ada2ae // indirect
+ github.com/SpineEventEngine/site-commons v0.0.0-20260226201948-3aec673b8046 // indirect
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20400 // indirect
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 // indirect
github.com/twbs/bootstrap v5.3.8+incompatible // indirect
diff --git a/docs/_preview/go.sum b/docs/_preview/go.sum
index 69c70e17db..f70140dc27 100644
--- a/docs/_preview/go.sum
+++ b/docs/_preview/go.sum
@@ -1,5 +1,5 @@
-github.com/SpineEventEngine/site-commons v0.0.0-20260225164144-d5e941ada2ae h1:pNjNP8lCXbbzArqX42kc2Rbj2LpqU18ltF+skxZ/xO4=
-github.com/SpineEventEngine/site-commons v0.0.0-20260225164144-d5e941ada2ae/go.mod h1:tkAl4StIREKmz9r5PiJtuDhvwMMkFXKWcaTyxhIikho=
+github.com/SpineEventEngine/site-commons v0.0.0-20260226201948-3aec673b8046 h1:qx3XD7j5i3xhtDu/iRLdqwT16BxHg+9Z7wtXae1VWRU=
+github.com/SpineEventEngine/site-commons v0.0.0-20260226201948-3aec673b8046/go.mod h1:tkAl4StIREKmz9r5PiJtuDhvwMMkFXKWcaTyxhIikho=
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20400 h1:L6+F22i76xmeWWwrtijAhUbf3BiRLmpO5j34bgl1ggU=
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20400/go.mod h1:uekq1D4ebeXgduLj8VIZy8TgfTjrLdSl6nPtVczso78=
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 h1:GZxx4Hc+yb0/t3/rau1j8XlAxLE4CyXns2fqQbyqWfs=
diff --git a/docs/content/docs/validation/00-intro/philosophy.md b/docs/content/docs/validation/00-intro/philosophy.md
index d2827f2104..0da8cbf498 100644
--- a/docs/content/docs/validation/00-intro/philosophy.md
+++ b/docs/content/docs/validation/00-intro/philosophy.md
@@ -123,8 +123,10 @@ front-end models, or JSON schemas.
Its focus is entirely on:
-```
-Protobuf → generated Java/Kotlin/TypeScript → domain logic
+```mermaid
+graph LR
+ A(Protobuf) --> B(Generated Java/Kotlin/TypeScript)
+ B --> C(Domain logic)
```
Everything else (frontend validation, OpenAPI, view models) should build on top
diff --git a/docs/content/docs/validation/01-getting-started/adding-to-build.md b/docs/content/docs/validation/01-getting-started/adding-to-build.md
index 74495d484a..8a3026ca63 100644
--- a/docs/content/docs/validation/01-getting-started/adding-to-build.md
+++ b/docs/content/docs/validation/01-getting-started/adding-to-build.md
@@ -82,7 +82,7 @@ Add the Validation plugin to the build.
```kotlin
plugins {
module
- id("io.spine.validation") version "2.0.0-SNAPSHOT.398"
+ id("io.spine.validation") version "2.0.0-SNAPSHOT.399"
}
```
diff --git a/docs/content/docs/validation/02-concepts/_index.md b/docs/content/docs/validation/02-concepts/_index.md
index 86bc83dcb5..54c4ec69e0 100644
--- a/docs/content/docs/validation/02-concepts/_index.md
+++ b/docs/content/docs/validation/02-concepts/_index.md
@@ -6,4 +6,105 @@ headline: Documentation
# Concepts
-_Draft. We will add content later._
+This page introduces the core vocabulary and mechanics of Spine Validation.
+It answers two questions:
+
+- **How do I express validation rules?**
+- **How does the runtime report violations?**
+
+If you are new to the library, start with [declaring constraints](../01-getting-started/first-model)
+in `.proto` files and then come back here for the details.
+
+
+## Vocabulary
+
+Spine Validation uses a small set of concepts consistently across code generation and runtime:
+
+- **Constraint** — a validation rule declared in Protobuf using an option (for example,
+ `(required)`, `(min)`, `(max)`, `(pattern)`, `(distinct)`, `(validate)`, `(set_once)`).
+- **Option application** — a concrete place where an option is applied (a field, a message,
+ a `oneof`, etc.).
+- **Violation** — an instance of a constraint being broken. Violations are represented
+ as `ConstraintViolation`.
+- **Validation error** — a collection of violations. Validation results are represented
+ as `ValidationError`.
+
+
+## Where constraints live
+
+Constraints are part of the model.
+You declare them next to message fields in your `.proto` files by importing
+`spine/options.proto`.
+
+This makes validation rules:
+
+- versioned together with the data model,
+- shared between services that reuse the same Protobuf definitions,
+- enforced automatically by generated code.
+
+
+## How validation is executed
+
+Validation is executed through **generated code** for your messages and builders.
+The generated API exposes two main ways to validate data:
+
+1. **Fail-fast validation on creation.**
+ If a constraint is violated, `build()` throws `ValidationException`.
+
+2. **Validation of an existing instance.**
+ Generated messages implement `ValidatableMessage` and provide `validate()`, which returns
+ `Optional`.
+
+See [Using the generated code](../01-getting-started/generated-code.md) for end-to-end examples.
+
+
+## What a violation contains
+
+Each `ConstraintViolation` points to the invalid value and explains what went wrong:
+
+- `field_path` — the path to the invalid field (for example, `contacts.email.value`).
+- `type_name` — the name of the validated root message type.
+- `message` — a templated, machine-friendly error message (`TemplateString`).
+- `field_value` — the invalid value packed as `google.protobuf.Any`.
+
+When you need a human-readable message, format the `TemplateString` from the violation.
+See [Working with error messages](error-messages.md) for message formatting,
+placeholders, and customization.
+
+
+## Nested validation and `(validate)`
+
+Protobuf messages often contain other messages.
+Spine Validation supports validating nested structures and reporting correct field paths.
+
+When a nested message is validated as part of another message’s validation, a violation:
+
+- keeps the **root** type in `type_name`, and
+- reports the **full path** to the invalid field in `field_path`.
+
+This allows you to surface errors as “`contacts.email.value` is invalid” while still knowing
+which message was validated at the top level.
+
+
+## Custom constraints
+
+If built-in options are not enough, you can add organization-specific options and generate
+code for them.
+
+See [Custom validation](../08-custom-validation/) for the workflow and a reference example.
+
+
+## What’s next
+
+- Declare rules in your model:
+ [Define constraints in `.proto` files](../01-getting-started/first-model.md).
+- Learn how runtime validation behaves:
+ [Using the generated code](../01-getting-started/generated-code.md).
+- See how options influence generated code:
+ [Options overview](options-overview.md).
+- Customize and format messages:
+ [Working with error messages](error-messages.md).
+- [Built-in options](../03-built-in-options/)
+- [Validating third-party messages](../04-third-party-messages/)
+- Add custom validation options:
+ [Custom validation](../08-custom-validation/).
diff --git a/docs/content/docs/validation/02-concepts/error-messages.md b/docs/content/docs/validation/02-concepts/error-messages.md
new file mode 100644
index 0000000000..ba00d250bd
--- /dev/null
+++ b/docs/content/docs/validation/02-concepts/error-messages.md
@@ -0,0 +1,142 @@
+---
+title: Working with error messages
+description: How validation error messages are built, customized, and formatted.
+headline: Documentation
+---
+
+# Working with error messages
+
+When a message violates validation constraints, Spine Validation reports violations as
+`ConstraintViolation` entries inside a `ValidationError`.
+
+Each violation contains a machine-friendly error message (`TemplateString`) which you can:
+
+- format for end users,
+- log for diagnostics, and
+- customize in your `.proto` model.
+
+
+## Where error messages come from
+
+Every built-in validation option defines a **default** error message.
+These messages come as the value of the `default_message` option declared for each option type.
+
+For example, the `pattern` option defines the following default message:
+
+```protobuf
+message PatternOption {
+
+ // The default error message.
+ option (default_message) = "The `${parent.type}.${field.path}` field"
+ " must match the regular expression `${regex.pattern}` (modifiers: `${regex.modifiers}`)."
+ " The passed value: `${field.value}`.";
+
+ // ...
+}
+```
+
+When you apply an option in a `.proto` file, you can **override** the default message by
+setting the option’s `error_msg` field.
+
+Example: custom message for a regex pattern.
+
+```protobuf
+import "spine/options.proto";
+
+message CreateAccount {
+ string id = 1 [
+ (pattern).regex = "^[A-Za-z0-9+]+$",
+ (pattern).error_msg = "ID must be alphanumerical in `${parent.type}`. Provided: `${field.value}`."
+ ];
+}
+```
+
+The placeholders (like `${field.value}`) are substituted at runtime when the violation is created.
+
+{{% note-block class="note" %}}
+Each option documents the placeholders it supports next to its `error_msg` field
+in `spine/options.proto`.
+{{% /note-block %}}
+
+## Placeholders and `TemplateString`
+
+`ConstraintViolation.message` is a `TemplateString`:
+
+- `with_placeholders` — a template string that may contain placeholders like `${field.path}`.
+- `placeholder_value` — a map from placeholder keys to their runtime values.
+
+The placeholder keys in the map do **not** include `${}` — for example, `field.path`.
+
+{{% note-block class="warning" %}}
+The map may include extra keys that are not referenced by the template, but every placeholder
+used in `with_placeholders` **must** have a corresponding value.
+Otherwise, the template is invalid.
+{{% /note-block %}}
+
+## Formatting messages in code
+
+To format a `TemplateString`, use:
+
+- Kotlin: `TemplateString.format()` / `TemplateString.formatUnsafe()`
+- Java: `TemplateStrings.format(TemplateString)` / `TemplateStrings.formatUnsafe(TemplateString)`
+
+`format()` validates that all placeholders have values and throws `IllegalArgumentException` otherwise.
+`formatUnsafe()` does not validate and leaves missing placeholders unsubstituted.
+
+{{< code-tabs langs="Kotlin, Java">}}
+
+{{< code-tab lang="Kotlin" >}}
+```kotlin
+val error = message.validate().orElse(null) ?: return
+val violation = error.constraintViolationList.first()
+val text = violation.message.format()
+```
+{{< /code-tab >}}
+
+{{< code-tab lang="Java" >}}
+```java
+var error = message.validate();
+if (error.isEmpty()) {
+ return;
+}
+var violation = error.get().getConstraintViolation(0);
+var text = TemplateStrings.format(violation.getMessage());
+```
+{{< /code-tab >}}
+
+{{< /code-tabs >}}
+
+## Choosing what to show vs what to log
+
+For **end-user** output:
+
+- format and display `ConstraintViolation.message`;
+- avoid leaking internal type names unless your product requires them.
+
+For **diagnostics** and support logs:
+
+- include `type_name` and `field_path` to pinpoint the location of the violation;
+- include the raw template (`message.with_placeholders`) and the placeholder map for debugging.
+
+
+## Troubleshooting formatting issues
+
+### `IllegalArgumentException` from `format()`
+
+This means `with_placeholders` references a placeholder that has no entry in `placeholder_value`.
+
+Recommended actions:
+
+- treat this as a bug in the option/message definition, and fix the custom `error_msg` in your `.proto`;
+- if you can tolerate partial substitution (for example, in logs), use `formatUnsafe()`.
+
+## What’s next
+
+- See how constraints are expressed as options and compiled into runtime checks:
+ [Options overview](options-overview.md).
+- Explore the built-in options:
+ [Built-in options](../03-built-in-options/).
+- Learn how to validate message types from third-party libraries:
+ [Validating third-party messages](../04-third-party-messages/).
+- If built-in options are not enough, define your own constraints and messages:
+ [Custom validation](../08-custom-validation/).
diff --git a/docs/content/docs/validation/02-concepts/options-overview.md b/docs/content/docs/validation/02-concepts/options-overview.md
new file mode 100644
index 0000000000..a304cca991
--- /dev/null
+++ b/docs/content/docs/validation/02-concepts/options-overview.md
@@ -0,0 +1,114 @@
+---
+title: Options overview
+description: Where validation options come from and how they influence generated code.
+headline: Documentation
+---
+
+# Options overview
+
+Spine Validation rules are expressed as **Protobuf options**.
+You annotate your `.proto` model with built-in options, and the **Validation Compiler**
+turns those option values into runtime checks in the generated Java code.
+
+This page explains where the built-in options come from, how they are applied at build time,
+and what API you get at runtime.
+
+
+## Where options come from
+
+The built-in validation options are defined in `spine/options.proto`.
+
+The file comes in the `spine-base.jar` artifact, which is an API dependency
+of the Validation runtime library (`spine-validation-java-runtime.jar`) added
+to your project by the Validation Gradle plugin.
+
+To use an option, import the proto that defines it and annotate your fields and messages.
+
+```protobuf
+import "spine/options.proto";
+```
+
+If you need a refresher on how custom options work in Protobuf, see
+[Protobuf custom options](https://protobuf.dev/programming-guides/proto3/#customoptions).
+
+
+## How options are applied (build time)
+
+Spine Validation is enforced by **generated code**, not by interpreting option values at runtime.
+
+At build time:
+
+1. `protoc` compiles your `.proto` files into **descriptors** and generates Java sources.
+2. The Spine Validation Gradle plugin wires the **Validation Compiler** into the build.
+3. The Validation Compiler reads the compiled descriptors (including your option values) and
+ augments `protoc` output with validation logic.
+
+The result is a Java API that enforces the rules you declared in the model.
+
+```mermaid
+flowchart LR
+ A[".proto
+
spine/options.proto"] --> B["protoc
(Java)"]
+ B --> C["Validation Compiler
via Gradle plugin"]
+ C --> D["Generated Java API
build(), validate()"]
+```
+
+## What code you get (runtime)
+
+Generated messages and builders provide a small validation-focused API surface:
+
+- `build()` performs validation and throws `ValidationException` if a constraint is violated.
+- `buildPartial()` builds without validation.
+- `validate()` checks an existing instance and returns `Optional`.
+
+See [Using the generated code](../01-getting-started/generated-code.md) for Java and Kotlin examples.
+
+
+## What does not happen
+
+At runtime, Spine Validation does **not** parse descriptor option data to decide what to validate.
+All checks are already translated into the generated message/builder code.
+
+
+## Tiny examples
+
+These examples are intentionally minimal.
+They are only meant to illustrate the “annotate with options → get runtime checks” flow.
+
+### Required field
+
+```protobuf
+import "spine/options.proto";
+
+message UserEmail {
+ string value = 1 [(required) = true];
+}
+```
+
+### String pattern
+
+```protobuf
+import "spine/options.proto";
+
+message OrderId {
+ string value = 1 [(pattern).regex = "^[A-Z]{3}-\\d{6}$"];
+}
+```
+
+### Numeric range
+
+```protobuf
+import "spine/options.proto";
+
+message Temperature {
+ int32 kelvin = 1 [
+ (min).value = "0",
+ (max).value = "10000"
+ ];
+}
+```
+
+
+## What’s next
+
+- [Built-in options](../03-built-in-options/)
+- [Custom validation](../08-custom-validation/)
diff --git a/docs/content/docs/validation/03-built-in-options/_index.md b/docs/content/docs/validation/03-built-in-options/_index.md
new file mode 100644
index 0000000000..6e186d9a85
--- /dev/null
+++ b/docs/content/docs/validation/03-built-in-options/_index.md
@@ -0,0 +1,12 @@
+---
+title: Built-in options
+description: Reference for built-in Spine Validation options.
+headline: Documentation
+---
+
+# Built-in options
+
+## What’s next
+
+- [Validating third-party messages](../04-third-party-messages/)
+- [Custom validation](../08-custom-validation/)
diff --git a/docs/content/docs/validation/04-third-party-messages/_index.md b/docs/content/docs/validation/04-third-party-messages/_index.md
new file mode 100644
index 0000000000..18665689ab
--- /dev/null
+++ b/docs/content/docs/validation/04-third-party-messages/_index.md
@@ -0,0 +1,12 @@
+---
+title: Validating third-party messages
+description: How to validate message types generated outside your build.
+headline: Documentation
+---
+
+# Validating third-party messages
+
+## What’s next
+
+- [Custom validation](../08-custom-validation/)
+- [Architecture](../09-developers-guide/architecture.md)
diff --git a/docs/content/docs/validation/08-custom-validation/_index.md b/docs/content/docs/validation/08-custom-validation/_index.md
index 91409e9a93..681741c310 100644
--- a/docs/content/docs/validation/08-custom-validation/_index.md
+++ b/docs/content/docs/validation/08-custom-validation/_index.md
@@ -25,6 +25,7 @@ Below is a workflow diagram for a typical option:
## What’s next
+- [Validating third-party messages](../04-third-party-messages/)
- Learn where this plugs in: [Architecture](../09-developers-guide/architecture.md).
Take a look at the `:tests:extensions` module that contains a full example of
diff --git a/docs/content/docs/validation/_index.md b/docs/content/docs/validation/_index.md
index 6ac06fcda9..3d075a52b3 100644
--- a/docs/content/docs/validation/_index.md
+++ b/docs/content/docs/validation/_index.md
@@ -16,5 +16,7 @@ options, and runs those checks automatically when you build messages.
## Deeper topics
+- [Built-in options](03-built-in-options/)
+- [Validating third-party messages](04-third-party-messages/)
- How it works: [Architecture](09-developers-guide/architecture.md)
- Extension points: [Custom validation](08-custom-validation/)
diff --git a/docs/data/docs/validation/2-0-0-snapshot/sidenav.yml b/docs/data/docs/validation/2-0-0-snapshot/sidenav.yml
index c1058569cb..907ffec735 100644
--- a/docs/data/docs/validation/2-0-0-snapshot/sidenav.yml
+++ b/docs/data/docs/validation/2-0-0-snapshot/sidenav.yml
@@ -31,6 +31,14 @@
children:
- page: Concepts
file_path: 02-concepts
+ - page: Options overview
+ file_path: 02-concepts/options-overview
+ - page: Working with error messages
+ file_path: 02-concepts/error-messages
+ - page: Built-in options
+ file_path: 03-built-in-options
+ - page: Validating third-party messages
+ file_path: 04-third-party-messages
- page: Custom validation
file_path: 08-custom-validation
- page: Developer’s guide
diff --git a/pom.xml b/pom.xml
index b36592557f..ed86aa7649 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject.
-->
io.spine.tools
validation
-2.0.0-SNAPSHOT.398
+2.0.0-SNAPSHOT.399
2015
diff --git a/version.gradle.kts b/version.gradle.kts
index 69fe447967..8b272b178d 100644
--- a/version.gradle.kts
+++ b/version.gradle.kts
@@ -29,4 +29,4 @@
*
* For Spine-based dependencies please see [io.spine.dependency.local.Spine].
*/
-val validationVersion by extra("2.0.0-SNAPSHOT.398")
+val validationVersion by extra("2.0.0-SNAPSHOT.399")