Last updated: 2026-09-21
Building Schemas and Scenarios: Design Notes
Working notes behind Schemas and Scenarios: why a schema’s clauses are ordinary compiled functions rather than inline text, and a checklist of what each stage of that page’s mechanism needed.
require/ensure Are Fatal, So a Schema Can’t Use Them
An earlier version of this page sketched a Schema:/Operation: block extension to PatLang’s Feature-file syntax, with require/ensure clauses written as inline text. Building it for real, rather than just imagining it, forced two corrections — both found by reading the actual PatLang source rather than trusting the sketch’s own assumptions:
require/ensure/assertare fatal on violation — confirmed directly inrust-runtime/src/ir/hosts.rs: a failed contract check aborts the running program. That rules out the sketch’s inline text clauses outright; a schema check has to report a diagnosis and keep running, not kill the process on the first violation. The real implementation represents a schema’s invariant, precondition, and postcondition as ordinary, separately-compiled PatLang functions returningbool, invoked by name through PatLang’s existingapply()primitive — the same mechanismself_hosting/lib/primitive_registry.patlangalready uses for named contract predicates elsewhere in the codebase, not new machinery.apply()has no argument-spread form. A checking function written once, generic over any schema’s own number of state variables, can’t pass one positional argument per variable — it has no way to know in advance how many there’ll be. State and inputs are therefore always bundled as a single list argument:invariant_fn(state_list),require_fn(state_list, input_list),ensure_fn(before_list, after_list, input_list).
Set and Map types didn’t exist either, as the sketch already suspected — self_hosting/lib/pset.patlang and pmap.patlang are new, small, association-list-based libraries built to fill exactly that gap, following the same list-plus-linear-scan idiom already used elsewhere in the codebase rather than inventing a new primitive.
What Each Stage Needed
| Piece | Status |
|---|---|
Feature:/Scenario:/Given/When/Then | Pre-existing PatLang syntax, unchanged |
Set<T>/Map<K,V> | Built: pset.patlang, pmap.patlang |
| Schema/operation declaration + binding | Built: schema_bdd.patlang (schema_define, schema_operation, schema_bind_state, schema_bind_input) |
| The witness check itself | Built: schema_check/schema_check_values, four-stage diagnosis |
| Self-healing synthesis hook | Built: an opt-in schema check in green_phase, zero effect unless registered |
| Inductive-synthesis bridge | Built: schema_synthesis_bridge.patlang |
GOAP state exposure (plan_with_state) | Built three times over: Rust interpreter, Rust codegen, and canonically as native self-hosted PatLang |
Clean interpreter run (world_run, interp_run) | Built: the synthesis engine tests candidate rules without a subprocess, so the induction example above runs in the browser |
| Declared-schema layer (explorer, feature analysis, outcomes, negative scenarios, sequences, refinement, generation) | Built: self_hosting/lib/zs_*.patlang |
Full source: the self_hosting/lib directory of the PatLang repository.