Layer 2 schemas
Five atom kinds — feature, ui_view, rule, criterion, fixture — carry a structured Behavior layer in addition to free-form Intent. Codegen tools lower these directly into target output.
The Zod schemas live in @atomprd/spec → behaviors.ts. Summaries below.
feature.behavior
Section titled “feature.behavior”{ trigger?: string; // optional event hook ("onClick", "onSubmit", ...) steps: Step[]; // required, may be [] onOk?: Step[]; // optional success-path tail onErr?: Step[]; // optional error-path tail}
type Step = | { kind: "validate"; rule: string; errorMessage?: string } | { kind: "call"; endpoint: string; method?: "GET"|"POST"|"PUT"|"DELETE"|"PATCH"; body?: unknown; assignTo?: string } | { kind: "dispatch"; feature: string; args?: Record<string, unknown> } | { kind: "setState"; path: string; value: unknown } | { kind: "navigate"; to: string; params?: Record<string, unknown> } | { kind: "log"; level: "debug"|"info"|"warn"|"error"; msg: string } | { kind: "if"; condition: string; then: Step[]; else?: Step[] }Lowered by @atomprd/codegen-senlang into a SenLang Action chain — one $do: ... per step. See Codegen → SenLang bridge.
ui_view.behavior
Section titled “ui_view.behavior”{ rootScreen: string; // ID of the entry screen screens: { [id: string]: { sections: Section[]; // ordered top-to-bottom }; };}
type Section = | { kind: "heading"; level: 1|2|3; text: string } | { kind: "text"; body: string } | { kind: "list"; source: string; itemTemplate: Section[] } | { kind: "form"; fields: { name: string; label?: string; type: "text"|"number"|"date"|"select"; options?: string[] }[]; submitLabel?: string; submitDispatch?: string } | { kind: "detail"; source: string; rows: { label: string; value: string }[] } | { kind: "button"; label: string; dispatch: string; variant?: "primary"|"secondary"|"danger" } | { kind: "input"; name: string; bind: string; placeholder?: string } | { kind: "switch"; bind: string; label: string };Lowered into a SenLang view tree. Multi-screen views compile to a top-level $Switch over a state slot.
rule.behavior
Section titled “rule.behavior”{ enforced_at: EnforcedAt[];}
type EnforcedAt = | { kind: "ui_disabled"; ui_view: string; binding: "submitButton" | string } | { kind: "pre_call"; feature: string; on_failure: "abort" | "warn" } | { kind: "validator"; field: string; entity: string };Lowered into SenLang rules[<slug>] plus bindings on the matching button / call site.
criterion.behavior
Section titled “criterion.behavior”{ before_after: { state_diff: Record<string, { before: unknown; after: unknown }>; api_calls: ApiCall[]; derived_diff?: Record<string, unknown>; };}
type ApiCall = { method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; endpoint: string; body?: unknown; response?: unknown;};Lowered into RFC-0010 scenario fixtures with given.state (the before snapshot), when array (the api_calls), and then block (the after snapshot).
fixture.behavior
Section titled “fixture.behavior”{ shape: "scenario" | "snapshot" | "stub"; before?: Record<string, unknown>; after?: Record<string, unknown>; api_calls?: ApiCall[];}Lowered into a sibling scenario file. When shape: "stub", the bridge emits a TODO scaffold.
Forward compatibility
Section titled “Forward compatibility”Tools encountering Layer 2 fields they don’t understand MUST preserve them as opaque jsonb. v0.4+ may extend Step / Section / EnforcedAt unions; existing tools should ignore unknown variants rather than reject.
See also
Section titled “See also”- Two-layer model — why these five kinds carry Layer 2.
- Provenance — per-field A / B / C / D map.
- Codegen → SenLang bridge — full lowering rules.