Skip to main content

When to Use

When validating any prompt unit — from CLI (stratt validate), CI pipeline, or programmatic checks. This is the single entry point for all unit validation.

The Pipeline (6 Steps)

validateUnit(parsed: unknown): ValidationResult
StepCheckFails on
1Zod structural parseMissing/invalid fields, wrong types, regex mismatches
2URI consistencyid field doesn’t decompose to match domain/type/slug/version
3Import formatAny import is not a valid strat:// URI
4CON-010 draft isolationStable unit (1.0.0+) imports draft unit (0.x.x)
5Lifecycle validityWarns (not fails) on author-set tampered status
6Forbidden block checkcomposition block present on role/rule/fragment
Steps 2-6 only run if step 1 passes (need valid shape to check semantics).

Return Type

type ValidationResult =
  | { success: true;  unit: Unit; warnings: string[] }
  | { success: false; errors: string[]; warnings: string[] }
Warnings are separate from errors — a warning does not block validation.

Key Design Decisions

Zod discriminated union: z.discriminatedUnion("type", [...]) dispatches to the correct branch based on the type field. If type is invalid, the error message explains which values are valid. Forbidden blocks checked against raw input: Zod’s .extend() strips unknown keys, so a composition block on a fragment would be silently removed. The pipeline checks "composition" in parsed on the raw input before Zod parsing discards it. Error accumulation: Steps 2-6 accumulate all errors rather than short-circuiting. The caller sees every problem at once. CON-010 heuristic: Draft versions are identified by version.startsWith("0."). This correctly identifies all 0.x.x versions per semver convention.

Usage

import { validateUnit } from "@stratt/schema";

const result = validateUnit(parsedYaml);
if (result.success) {
  // result.unit is typed as Unit (discriminated union)
  console.log(result.unit.type); // "role" | "rule" | "task" | "chain" | "fragment"
} else {
  console.error(result.errors); // string[]
}