= core-validation

== purpose

Runs a pipeline of validators against a `ValidationContext` (containing the workflow graph, detected cycles, cycle policies, and session state) to produce a `ValidationReport`. If structural errors are found, the pipeline rejects immediately as non-retryable. If issues warrant human review, it signals `NeedsApproval`.

== responsibilities

* Validating workflow graph structure (start node exists, no dangling transitions)
* Validating transition integrity (endpoint references, deterministic ordering)
* Validating session temporal and status consistency
* Running semantic rules (e.g. cycle policy binding checks)
* Validating artifact payloads against their declared schemas
* Producing a `ValidationOutcome` (Passed, Rejected, or NeedsApproval)
* Determining whether validation failures are retryable
* Triggering approval requests when validation risk exceeds thresholds

== non-responsibilities

* Does not enforce approvals or block execution — only signals the need for approval
* Does not modify state or emit events — it is a pure validation function
* Does not manage the lifecycle of validation reports
* Does not validate LLM outputs directly — validates artifacts and graph structures

== key types

=== Validator
* **kind**: fun interface
* **purpose**: Performs a single validation pass and returns a `ValidationSection`.

=== ValidationPipeline
* **kind**: class
* **purpose**: Runs a list of validators in sequence. Short-circuits on structural errors (non-retryable rejection). Optionally evaluates an `ApprovalTrigger` after all validators pass.
* **fields**: `validators` (list), `approvalTrigger` (nullable)

=== ValidationOutcome
* **kind**: sealed interface
* **purpose**: Result of a validation run.
* **variants**:
  * `Passed(report)` — validation succeeded
  * `Rejected(report, retryable)` — validation failed; `retryable` is set by the validator and must not be overridden
  * `NeedsApproval(request)` — validation passed but human approval is required

=== ValidationReport
* **kind**: data class
* **purpose**: Collection of `ValidationSection`s produced by a pipeline run.

=== ValidationSection
* **kind**: data class
* **purpose**: Named group of issues from a single validator.
* **fields**: `name`, `issues`, `metadata`

=== ValidationIssue
* **kind**: data class
* **purpose**: A single finding with machine-readable code, human message, severity, and optional location.

=== ValidationSeverity
* **kind**: enum
* **variants**: `INFO`, `WARNING`, `ERROR`

=== ValidationLocation
* **kind**: sealed interface
* **purpose**: Points to where an issue was found.
* **variants**:
  * `Graph(stageId, transitionId)` — issue in workflow graph
  * `Session(sessionId)` — issue in session state

=== ValidationContext
* **kind**: data class
* **purpose**: Input to validators, containing the workflow graph, cycles, policies, and session state.
* **fields**: `graph` (WorkflowGraph), `detectedCycles`, `cyclePolicies`, `sessionState`

=== GraphValidator
* **kind**: class
* **purpose**: Checks start node existence, dangling transitions, and records detected cycles as INFO issues.

=== TransitionValidator
* **kind**: class
* **purpose**: Checks transition endpoint integrity and deterministic ordering per source node.

=== SessionValidator
* **kind**: class
* **purpose**: Checks temporal consistency, negative invalid transition count, and suspicious failure states.

=== SemanticValidator
* **kind**: class
* **purpose**: Runs a list of `SemanticRule` instances.

=== SemanticRule
* **kind**: interface
* **purpose**: Validates a single semantic property.

=== CyclePolicyBindingRule
* **kind**: class
* **purpose**: Warns when detected cycles lack a policy binding. Only active when `requirePolicyForCycles` is true.

=== ArtifactPayloadValidator
* **kind**: class
* **purpose**: Validates artifact payloads (`FileWrittenArtifact`, `ProcessResultArtifact`) against their schemas by reading from `ArtifactStore`.

=== ApprovalTrigger
* **kind**: class
* **purpose**: Evaluates a `ValidationReport` and returns an `ApprovalRequest` if error count > 0 or cycle policies are missing.

=== ApprovalRequest (validation)
* **kind**: data class
* **purpose**: Signals that an operation requires human approval.
* **fields**: `sessionId` (SessionId?, nullable — may be absent at early validation stage), `riskSummary` (ValidationRiskStats), `validationReport`

=== ValidationRiskStats
* **kind**: data class
* **purpose**: Summary of error and warning counts plus cycle policy status.

== event flow

*inbound:*
* None. Validation is invoked by the orchestrator before executing operations. It does not subscribe to events.

*outbound:*
* None. Validation is a pure function — it returns a sealed outcome, it does not emit events.

== integration points

* `:core:events` — shared identity types (SessionId, StageId, etc.)
* `:core:sessions` — `SessionState`, `SessionStatus`
* `:core:transitions` — `WorkflowGraph`, `DetectedCycle`, `CyclePolicyBinding`, `TransitionEdge`, `TransitionOrdering`, `CycleSignatureFactory`
* `:core:artifacts` — `FileWrittenArtifact`, `ProcessResultArtifact`, `TypedArtifactSlot`
* `:core:artifacts-store` — `ArtifactStore`

== invariants

* `ValidationOutcome.Rejected.retryable` is set by the validator and must not be overridden by the orchestrator
* Structural validation errors (ERROR severity in the graph) produce `retryable = false`
* Pipeline short-circuits on the first ERROR severity issue — remaining validators are skipped
* `ApprovalRequest` produced by `ApprovalTrigger` is implicitly `Tier.T2 (REVERSIBLE)` — validation affects workflow progression, not external state
* The `RiskSummary` and `ValidationRiskStats` types are duplicate definitions of the same data

== PlantUML diagram

[plantuml, core-validation, "png"]
----
include::../../diagrams/core-validation.puml[]
----




== known issues

* `RiskSummary` and `ValidationRiskStats` are separate data classes with identical fields — likely a leftover from refactoring.
* `ApprovalRequest` lives in the validation module but is consumed by the approvals module, which has its own `DomainApprovalRequest` type. These are two different representations of the same concept with no shared type.

== open questions

* Should the `ApprovalRequest` produced by validation share a common type with the approvals module instead of being a validation-local class?
* Should validation produce structured IDs for reports and risk summaries that are meaningful to the approvals module?
