Cornerstone Engineering Monograph · UK Patent Pending
Recursive Delta-Hydration: Multi-Turn Progressive Validation and Leaf-Locking on Nested Pydantic v2 Models
How Blockstate synthesises recursive PartialSlotModel schemas with a singleton UNSET sentinel, validates leaf paths individually (BLK-VAL-002), locks validated leaves (BLK-SEC-002), and halts immediately on DisqualifyingBreachError (BLK-VAL-003).
1. The Multi-Turn Structured Output Problem
Single-turn structured output libraries work well when a user provides every required field in one prompt. In real enterprise workflows, however, a customer filing an insurance claim, applying for a mortgage, or completing clinical triage supplies data across three to six conversational turns.
Consider a nested Pydantic v2 model for a motor insurance claim:
from datetime import date
from pydantic import BaseModel, Field, field_validator
from blockstate import BreachError
class IncidentLocation(BaseModel):
street: str = Field(description="Street name where the incident occurred")
city: str = Field(description="Town or city")
postcode: str = Field(pattern=r"^[A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}$")
class MotorClaimSlots(BaseModel):
policy_number: str = Field(pattern=r"^POL-\d{6}$")
vehicle_usage: str = Field(description="personal | commuting | commercial_haulage")
incident_date: date = Field(description="Date of incident (YYYY-MM-DD)")
estimated_damage_gbp: float = Field(gt=0)
location: IncidentLocation
@field_validator("incident_date", mode="after")
@classmethod
def validate_not_in_future(cls, v: date) -> date:
if v > date.today():
raise ValueError("Incident date cannot be in the future.")
return v
@field_validator("vehicle_usage", mode="after")
@classmethod
def check_commercial_eligibility(cls, v: str) -> str:
if v.lower() == "commercial_haulage":
raise BreachError("Commercial haulage policies require the Specialist Fleet Desk.")
return v
If a customer says on Turn 1:
"Hi, my policy is POL-884120, personal use, and someone bumped my car in Manchester (M1 1AG), date 2099-01-01."
standard MotorClaimSlots.model_validate(...) fails in three ways:
location.streetandestimated_damage_gbpare missing, causing the entire model instantiation to fail.- Because
incident_date(2099-01-01) failsvalidate_not_in_future, standard batch validation discards the four valid fields (policy_number,vehicle_usage,location.city,location.postcode) that were supplied in the same sentence. - Creating manual
Optional[...]mirror classes for every nested model loses distinction between "the user has not provided this field yet" and "the user explicitly providedNone".
2. Recursive PartialSlotModel Synthesis with the Singleton UNSET Sentinel (Claim 7)
When you bind a Pydantic v2 model to a stage via @Stage(collect=MotorClaimSlots), Blockstate (src/blockstate/core/slots.py) inspects the model hierarchy at compile/load time:
- Nesting Depth Verification (
BLK-CFG-002): The compiler verifies that the nestedBaseModeltree does not exceedmax_nesting_depth(defaultdepth=8), preventing deeply nested denial-of-service schemas. - Recursive
UNSETRelaxation: Blockstate dynamically synthesises a recursivePartialSlotModelwhere every leaf field typeTis relaxed toT | UnsetType, defaulting to the singletonUNSETsentinel rather thanNone. Nested child models (IncidentLocation) are recursively replaced with their partial counterparts (PartialIncidentLocation).
flowchart TD
subgraph TurnInput["Turn 1 Candidate Extraction"]
RAW["Candidate Dict from Pass 1:<br/>policy_number='POL-884120', vehicle_usage='personal',<br/>incident_date='2099-01-01', location.city='Manchester',<br/>location.postcode='<VAULT:POSTCODE_01>'"]
end
subgraph HydrationEngine["Recursive Delta-Hydration Engine (slots.py)"]
RAW --> FLAT["Flatten to Dot-Separated Leaf Paths"]
FLAT --> BREACH{"Check DisqualifyingBreachError<br/>(BLK-VAL-003)"}
BREACH -->|"Raised (e.g. commercial_haulage)"| EXIT["Immediate Stage Exit<br/>Route to @Stage(on_breach=...)"]
BREACH -->|"No Breach"| LEAF["Individual Per-Leaf Validation<br/>(BLK-VAL-002)"]
LEAF -->|"Valid Leaf"| LOCK["Commit to Hydrated State &<br/>Add to locked_slots (BLK-SEC-002)"]
LEAF -->|"Invalid Leaf (incident_date)"| REJECT["Record Leaf Error for Re-Elicitation<br/>Preserve Valid Sibling Leaves!"]
LOCK & REJECT --> DELTA["Compute Delta = Required \ Hydrated<br/>{incident_date, location.street, estimated_damage_gbp}"]
end
3. Individual Flattened Leaf-Path Validation (BLK-VAL-002) & Leaf Locking (BLK-SEC-002)
Rather than validating the candidate payload as an all-or-nothing monolith, Blockstate flattens the candidate dictionary into dot-separated leaf paths (policy_number, vehicle_usage, incident_date, location.city, location.postcode) and validates each leaf path individually (BLK-VAL-002):
- Preserving Valid Sibling Leaves: In our Turn 1 example,
policy_number,vehicle_usage,location.city, andlocation.postcodepass their type and regex checks. They are immediately committed to the frame's hydrated slot store and added tolocked_slots(BLK-SEC-002). - Targeted Invalid Leaf Rejection: Only
incident_date(2099-01-01) failsvalidate_not_in_future. Blockstate records the validation error specifically forincident_datewhile keeping its four valid siblings locked. - Computing $\Delta = \text{Required} \setminus \text{Hydrated}$: At the end of the turn, the engine calculates the exact set of remaining unhydrated leaf paths:
$$\Delta = \{\text{incident\_date}, \text{location.street}, \text{estimated\_damage\_gbp}\}$$
Pass 2 is instructed to explain the date validation error and prompt the customer strictly for those three remaining fields.
4. Immediate Halt on DisqualifyingBreachError (BLK-VAL-003)
In regulated workflows, certain inputs immediately disqualify the customer from the current workflow (for example, declaring vehicle_usage = "commercial_haulage" on a consumer motor policy). Continuing to ask the customer for their street name and repair quote after they have already stated a disqualifying condition wastes their time and creates compliance risk.
When any leaf validator raises DisqualifyingBreachError (aliased as BreachError, BLK-VAL-003):
- The hydration loop halts immediately without evaluating remaining leaves.
- The engine bypasses normal $\Delta$ re-prompting and transitions deterministically to the stage specified in
@Stage(on_breach="commercial_fleet_desk"). - Because
on_breachis declared statically on@Stage,blockstate checkandblockstate verifyinclude theintake_claim -> commercial_fleet_deskedge in the compile-time Control Flow Graph.
5. Property-Based Fuzzing of $\Delta$-Hydration
In our test suite, Hypothesis (max_examples=500) generates arbitrary multi-level Pydantic v2 model trees and random multi-turn sequences of partial, valid, invalid, and breach inputs, verifying that:
- Validated leaves in
locked_slotsare never overwritten across turns (BLK-SEC-002); - Valid sibling leaves are never lost when a co-supplied leaf fails validation (
BLK-VAL-002); and - The strict target
BaseModelinstantiates if and only if $\Delta = \emptyset$.
Statutory Notice: Blockstate is UK Patent Pending ("Process execution control system", UKIPO priority filing 28 October 2026, UK Patents Act 1977 s.62(1)) and distributed under a Source-Available Dual Licence (LICENSE & PATENTS.md).
MappingProxyType tool gatekeeper, and TaskStack digression call stack live in the Blockstate Signal Box Simulator. Distributed under the Blockstate Source-Available Dual Licence (Free £0 for local dev, open-source CI/CD verification via blockstate verify, and 90-day internal enterprise staging evaluation; paid commercial licence for live production).