When to Use
When adding a new prompt unit type, modifying an existing type’s required/optional fields, or extending the base schema shared by all types.Package Location
packages/schema/ in the stratt-run repo. Source of truth for all unit validation.
Architecture
How to Add a New Unit Type
- Add to constants.ts: Add the type name to
UNIT_TYPESarray. Add its SPUH bit value toTYPE_BITS. - Create type file:
src/types/{newtype}.ts— extendBaseSchemawithtype: z.literal("newtype")plus type-specific blocks. - Update types/index.ts: Import the new schema, add to
z.discriminatedUnion("type", [...]), export schema and type. - Update validate.ts: Add any forbidden block checks (e.g., composition forbidden for your type).
- Update spuh.ts constants: Ensure
BITS_TO_TYPEreverse map covers the new bit value. - Write tests: At least 3 tests — valid parse, valid with optionals, deliberate failure.
- Update barrel: Re-export from
src/index.ts.
Pattern: Extending BaseSchema
.extend() method overrides the base type: z.enum(UNIT_TYPES) with the specific literal, enabling discriminated union dispatch.
Pattern: Required vs Optional vs Forbidden Blocks
| Block | Required for | Optional for | Forbidden for |
|---|---|---|---|
| contract | task, chain | role, rule, fragment | — |
| composition | chain | — | role, rule, fragment |
| persona | role | — | — |
| rule_block | rule | — | — |
| prompt_body | task | — | — |
| fragment_body | fragment | — | — |
| council | task, chain | role, rule | — |
validate.ts against the raw input object.
Gotcha: Zod .default() Behaviour
z.boolean().default(false) injects false during .parse() when the key is absent. This means the parsed output always has the field — test both explicit and omitted cases.