CRUMB a card from devarno-cloud

JSON Schema 2020-12 & Validation Pipeline

iris intermediate 5 min read

ELI5

JSON Schema is like a strict teacher who checks your homework against a detailed answer key. iris.schema.json is the answer key for IRIS documents. It says exactly what fields are required, what patterns names must follow, and how everything fits together. The validation pipeline is the teacher’s red pen — it marks every mistake so you can fix it before submitting.

Technical Deep Dive

Schema Overview

flowchart TD
A["iris.schema.json<br/>JSON Schema Draft 2020-12"] --> B["$defs: Shared types"]
A --> C["oneOf: Top-level discriminator"]
C --> D["Sprite"]
C --> E["Council"]
C --> F["Chain"]
B --> G["UUID, Semver, SpriteName, HexHash"]
B --> H["Capability, TestCase, ChainStep"]
B --> I["Gate, Rule, Fingerprint, SpriteMetadata"]

Top-Level Discriminator

A valid IRIS document must be exactly one of three types, discriminated via oneOf:

TypeDiscriminating FieldRequired Fields
Spritesystem_promptid, name, version, capabilities, system_prompt, metadata, fingerprint
Councilspritesid, name, domain, sprites, chains, gate_agents, rules
Chainstepsid, name, steps, gates, timeout

$defs: Shared Type Definitions

classDiagram
class SharedDefs["$defs"] {
+UUID
+Semver
+SpriteName
+HexHash
+Duration
+Fingerprint
+SpriteMetadata
+Capability
+TestCase
+ChainStep
+Gate
+Rule
}
TypePattern / FormatExample
UUID^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$550e8400-e29b-41d4-a716-446655440000
Semver^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([...]))?(?:\+([...]))?$1.0.0, 2.1.0-alpha+build.123
SpriteName^[A-Z][A-Z0-9]*(-[A-Z0-9]+)*$SOL-FORGE, CORTEX-01
HexHash^[0-9a-f]{64}$a1b2c3d4... (64 chars)
Duration^([1-9]\d*)(ms|s|m|h)$30s, 5m, 1h

Validation Pipeline

flowchart TD
A["Input Document"] --> B{"Format?"}
B -->|YAML| C["PyYAML parse"]
B -->|JSON| D["json.loads"]
C --> E["Python dict"]
D --> E
E --> F["jsonschema.Draft202012Validator"]
F --> G{"$ref resolution"}
G --> H["Resolve against #/$defs/*"]
H --> I["Validate oneOf discriminator"]
I --> J{"Sprite?"}
I --> K{"Council?"}
I --> L{"Chain?"}
J --> M["Validate Sprite schema"]
K --> N["Validate Council schema"]
L --> O["Validate Chain schema"]
M --> P{"Valid?"}
N --> P
O --> P
P -->|Yes| Q["Return: valid=true"]
P -->|No| R["Return: errors[]"]

Validation in Python SDK

from iris.sprite import Sprite
# Strict validation
sprite = Sprite.from_file("sprite.yaml")
is_valid = sprite.validate(strict=True)
# Lenient validation (auto-converts string capabilities, strips extra fields)
is_valid = sprite.validate(strict=False)

Lenient mode behaviour:

  • String capabilities are auto-converted to capability objects
  • tests are excluded from validation
  • Extra fields not in the schema are stripped

Validation in MCP Server

The validate_sprite MCP tool performs client-side field-by-field validation:

function validateSprite(sprite): ValidationResult {
const errors: ValidationError[] = [];
// Name: required, matches SOL-[A-Z]+-\d+
if (!sprite.name || !/^SOL-[A-Z]+-\d+$/.test(sprite.name)) {
errors.push({ field: "$.name", message: "Invalid sprite name", severity: "error" });
}
// Version: required, semver
if (!sprite.version || !/^\d+\.\d+\.\d+$/.test(sprite.version)) {
errors.push({ field: "$.version", message: "Invalid version", severity: "error" });
}
// Role: one of 6 valid roles
const validRoles = ["architect", "reviewer", "documenter", "operator", "test-architect", "planner"];
if (!validRoles.includes(sprite.role)) {
errors.push({ field: "$.role", message: "Invalid role", severity: "error" });
}
// Capabilities: array, min 1, each has name + description
if (!Array.isArray(sprite.capabilities) || sprite.capabilities.length < 1) {
errors.push({ field: "$.capabilities", message: "At least 1 capability required", severity: "error" });
}
// System prompt: required, non-empty string
if (!sprite.system_prompt || typeof sprite.system_prompt !== "string" || sprite.system_prompt.length === 0) {
errors.push({ field: "$.system_prompt", message: "System prompt required", severity: "error" });
}
return {
valid: errors.filter(e => e.severity === "error").length === 0,
errors,
schema_version: "1.0.0"
};
}

Schema Change Process

Per GOVERNANCE.md, schema changes follow a 7-step process:

  1. Propose via PR to iris-specs
  2. Update iris.schema.json
  3. Update all affected example sprites
  4. Run validation scripts against all examples
  5. Update CHANGELOG.md
  6. Maintainer review + approval
  7. Downstream repos update their schema dependency

Key Terms

  • JSON Schema Draft 2020-12 → The specification version used by IRIS; supports oneOf, $defs, and advanced validation
  • $defs → A schema section defining reusable types (UUID, Semver, SpriteName, etc.)
  • oneOf discriminator → Ensures a document matches exactly one of Sprite, Council, or Chain
  • $ref resolution → Referencing shared type definitions within the schema (e.g., $ref: "#/$defs/Capability")
  • Lenient validation → SDK mode that auto-fixes minor issues (string→object conversion) for developer convenience
  • Schema propagation → The process of updating downstream repos when iris.schema.json changes

Q&A

Q: Can I add custom fields to a sprite? A: The JSON Schema sets additionalProperties: false on Sprite, Council, and Chain. Extra fields are rejected in strict mode and stripped in lenient mode.

Q: What happens if a $ref can’t be resolved? A: jsonschema raises a RefResolutionError. The SchemaLoader loads the full schema with all $defs to prevent this.

Q: How does the schema enforce unique capabilities? A: The capabilities array uses uniqueItems: true. Duplicate capability names within a sprite are rejected.

Q: Can I use a different JSON Schema draft? A: No. IRIS standardises on Draft 2020-12. Using a different draft would break $ref resolution and validation behaviour.

Q: What is the relationship between iris.schema.json and iris-api.openapi.yaml? A: iris.schema.json defines the data model. iris-api.openapi.yaml defines the REST API that operates on those models. Both are contracts consumed by SDKs and services.

Examples

JSON Schema validation is like a passport control system:

  • UUID regex = The machine-readable zone format (exactly 36 characters, specific dash positions)
  • SpriteName regex = The country code rules (must be 2–3 uppercase letters)
  • Semver regex = The passport number format (must follow a specific pattern)
  • oneOf discriminator = The gate that determines if you’re a citizen, resident, or visitor — you must be exactly one
  • $defs = The shared reference book all border agents use (same definitions everywhere)
  • Validation errors = The reasons you’re sent to secondary inspection (photo doesn’t match, visa expired, wrong document type)
  • Lenient mode = The helpful agent who says “your photo is a bit dark, but I can still verify it’s you” instead of sending you away

neighbors on the map