Skip to content
Prompting Press v0.5

Rust API reference

pub struct DeriveOptions

Options for [Prompt::derive_with]. Implements [Default] so callers use DeriveOptions { strategy: MergeStrategy::Merge,..Default::default } — forward- extensible without a breaking signature change when future options are added.

pub strategy: MergeStrategy

The merge strategy to apply. Defaults to [MergeStrategy::Replace].

pub struct Prompt

An immutable, fully-validated prompt. Wraps a [PromptDefinition]; all invariants (shape-valid, template-parseable, agreement-sound, reserved-name clean) are enforced at construction time. There are no setters; the sole mutator is [Prompt::derive].

pub fn body(&self) -> &str

The root body template source (the default arm’s unrendered template).

pub fn check(&self) -> CheckReport

Pure advisory lint: returns a [CheckReport] containing only the trust/guard finding class. Construction already enforces agreement, parse, and reserved-name invariants, so those arms are structurally unreachable for a constructed Prompt. The only LIVE finding check can surface is [FindingKind::UntrustedWithoutGuard] — a prompt declaring trusted: false vars but carrying no "guard" key in metadata. Pure: takes &self, never renders, never mutates.

pub fn definition(&self) -> &PromptDefinition

Borrow the underlying [PromptDefinition] for use by binding crates (e.g. prompting-press-node, prompting-press-py) that need to call the kernel directly for render/get_source (their validation is owned in the binding layer, not in Rust garde, so the consumer’s generic render<V> is not usable there). Bindings call prompting_press_core::render(prompt.definition,...) directly after doing their own validation — the same zero-engine-logic pattern as the Prompt::render path. Also used by Composition::resolve within this crate.

pub fn derive(&self, overlay: PromptOverlay) -> Result<Self, ConsumerError>

The sole mutator: shallow-replace top-level fields from overlay onto a clone of this prompt’s definition, then route the merged definition through [Prompt::new] (full re-validation). The original Prompt is untouched. Each Some(field) in overlay replaces the corresponding field; None fields are left as-is. name IS overlayable (the overlay can rename a prompt). After the merge, every construction invariant is re-checked over the whole merged definition — so an overlay that introduces an agreement violation or a reserved variant name is rejected. Uses [MergeStrategy::Replace] (the default): each overlay-present map field replaces the base’s map field wholesale — byte-identical to the pre-017 behavior. In Rust the validator is the generic V named at the render call site (garde covers all fields at compile time); derive takes &self and carries no runtime validator. PromptOverlay therefore contains only data fields. # Errors Same error classes as [Prompt::new]: a merged definition that fails any construction invariant returns the structured error.

pub fn derive_with(&self, overlay: PromptOverlay, options: DeriveOptions) -> Result<Self, ConsumerError>

Strategy-aware mutator: merge overlay onto this prompt’s definition using the selected [MergeStrategy], then re-validate the whole merged definition. - [MergeStrategy::Replace] — byte-identical to derive. Each overlay-present field replaces the base’s field wholesale (the default). - [MergeStrategy::Merge] — for the three map-typed fields (variables, variants, metadata) performs a top-level key union with child-wins-whole-entry on collision (no recursion). Scalar fields (name, role, body, output_model) still replace when overlay-present. Reserved axes deep/none are excluded. The merged definition is always routed through [Prompt::new] (full re-validation: agreement check, template parse, reserved-name check) — no invariant is weakened by the strategy choice. The original Prompt is untouched (immutability). # Errors Same error classes as [Prompt::new]: a merged definition that fails any construction invariant returns the structured error.

pub fn from_json(text: &str) -> Result<Self, ConsumerError>

Deserialize a Prompt from already-read JSON text, then validate. Equivalent to serde_json::from_str(..) + [Prompt::new]. Error semantics mirror from_yaml. # Errors [ConsumerError::Load] if text is not valid JSON or does not match the PromptDefinition shape, or any error from [Prompt::new].

pub fn from_toml(text: &str) -> Result<Self, ConsumerError>

Deserialize a Prompt from already-read TOML text, then validate. Uses toml::from_str (the serde-native TOML crate — research R3 / toml@1.1.2). Error semantics mirror from_yaml. # Errors [ConsumerError::Load] if text is not valid TOML or does not match the PromptDefinition shape, or any error from [Prompt::new].

pub fn from_yaml(text: &str) -> Result<Self, ConsumerError>

Deserialize a Prompt from already-read YAML text, then validate. Equivalent to serde_yaml_ng::from_str(..) + [Prompt::new]. A parse/shape error returns [ConsumerError::Load]; a validation error returns the same errors as new. The crate reads no files — the caller supplies already-read text. # Errors [ConsumerError::Load] if text is not valid YAML or does not match the PromptDefinition shape, or any error from [Prompt::new].

pub fn get_source(&self, variant: Option<&str>) -> Result<&str, ConsumerError>

Return a variant’s unrendered template source (the exact string the kernel hashes into template_hash). Delegates to the kernel; no vars, no validation. variant = None returns the root body source. # Errors [ConsumerError::Kernel] — the kernel rejected the lookup (unknown variant name).

pub fn metadata(&self) -> &Map<String, Value>

The metadata opaque map (library-defined top-level annotations, if any).

pub fn name(&self) -> &str

The prompt’s name (the name field of the underlying definition).

pub fn new(def: PromptDefinition) -> Result<Self, ConsumerError>

The primary validating constructor. Runs the construction invariants on def: 1. For each variant arm (root body + every named variant), asks the kernel for the arm’s [required_roots]. A kernel Err (parse failure or excluded feature) is a construction failure — the Prompt is not built. 2. Each analyzable arm’s referenced roots must be a subset of the declared variables. Any root not declared is an agreement violation. 3. A variant named literally "default" is rejected — the kernel reserves that name for the root body; the declared arm would be unreachable. On success the Prompt is returned. On any violation a structured [ConsumerError] is returned — never a panic. # Errors - [ConsumerError::Kernel] — a variant template could not be parsed or uses an excluded feature (&#123;% include %&#125; / macros / inheritance). - [ConsumerError::Kernel] — a variant template references a variable not declared in variables (agreement failure; code::UNDEFINED_VARIABLE). - [ConsumerError::Kernel] — a variant is literally named "default" (reserved; code::UNDEFINED_VARIABLE with field "variant").

pub fn output_model(&self) -> Option<&str>

The output model reference, if declared (output_model field). Carried as metadata only — never parsed or resolved by this library.

pub fn render<V>(&self, vars: &V, variant: Option<&str>, guard: &GuardConfig, reveal_render_detail: bool) -> Result<RenderResult, ConsumerError>
where
V: Serialize + Validate,
<V as Validate>::Context: Default

Validate-then-render this prompt. 1. Validates vars once via garde, BEFORE any templating. On failure returns [ConsumerError::Validation] — the kernel is never reached. 2. Bridges the validated struct to the kernel’s value type via [minijinja::Value::from_serialize]. 3. Delegates to [prompting_press_core::render], normalizing any [KernelError] to [ConsumerError::Kernel]. variant = None selects the default (root body) arm. guard is plumbed straight through to the kernel; RenderResult::guard is surfaced unchanged (guard expansion is the kernel’s contract). V::Context: Default so the whole-struct [Validate::validate] convenience applies (one validation pass over the entire input set). Context-carrying validation is intentionally out of v1 scope (one concrete path per concern). ## Byte-identical output The RenderResult hashes are byte-identical across Rust, Python, and TypeScript for the same template and inputs, because all three bindings share this kernel render path. ## reveal_render_detail — unsafe, off-by-default render-error detail opt-in Pass false in all production call sites (the default). When true, the full underlying render-error detail is surfaced in the returned [ConsumerError::Kernel] message instead of the fixed scrubbed string. Risk: enabling this may place bound-value content — untrusted input, PII, secrets — into the returned error message and into any log line or stack trace derived from it. Use only in a controlled debug context where you own the log destination and deliberately accept that exposure. Never set true by default or in ambient/global configuration. # Errors - [ConsumerError::Validation] — garde rejected vars. - [ConsumerError::Kernel] — the kernel rejected the render (unknown variant, strict-undefined reference, parse/render failure). Render detail scrubbed unless reveal_render_detail = true. Parse detail always preserved.

pub fn role(&self) -> &PromptDefinitionRole

The conversational role (system / user / assistant).

pub fn variables(&self) -> &HashMap<String, PromptVariable>

The declared variables map (name → PromptVariable).

pub fn variants(&self) -> &HashMap<String, PromptVariant>

The named variants map (name → PromptVariant). Empty when the prompt has no named variants (only the implicit default arm).

pub struct PromptOverlay

A shallow-replacement overlay for [Prompt::derive]. Each field is Option<T>. A Some(value) replaces the corresponding field on the cloned definition; a None leaves it unchanged. All fields are optional — pass only what should change. name is overlayable: a prompt can be renamed (useful for template-derived variants). After merging, the full construction invariants (agreement, parse, reserved name) are re-checked over the merged whole. In Rust the validator is the generic V named at the call site; PromptOverlay carries only data fields — no runtime validator object (the Rust compile-time asymmetry documented in R6).

pub body: Option<String>

Replace the root body template source.

pub metadata: Option<Map<String, Value>>

Replace the metadata opaque map.

pub name: Option<PromptDefinitionName>

Replace the prompt’s name.

pub output_model: Option<Option<String>>

Replace (or clear) the output_model reference.

pub role: Option<PromptDefinitionRole>

Replace the prompt’s role.

pub variables: Option<HashMap<String, PromptVariable>>

Replace the full variables map.

pub variants: Option<HashMap<String, PromptVariant>>

Replace the full variants map.

pub enum MergeStrategy

Selects how [Prompt::derive_with] combines map-typed overlay fields with the base. Two strategies are supported in this release (the merge/replace industry-standard pair — RFC 7386 JSON Merge Patch, Kubernetes, Terraform merge). The enum is designed so a future value (e.g. Deep) can be added without a new method or a breaking signature change (C-08 — reserved axis, earned by a future second consumer). ## Semantics - Replace — each overlay-present top-level field replaces the base’s field wholesale. This is the default and reproduces the pre-017 behavior exactly. - Merge — for the three map-typed fields (variables, variants, metadata) performs a top-level key union with child-wins-whole-entry on collision (no recursion into entry contents — that would be the excluded deep strategy). Scalar fields (name, role, body, output_model) still replace when overlay-present. ## Soundness boundary The agreement check is name-only (required_roots ⊆ declared variable names). Consequently: - A Merge that removes a variable a base variant body still references fails construction via the agreement check (name-removal caught across all variant arms). - A Merge that replaces a variable’s declaration (changing its type or trusted flag) is accepted — type/trust correctness is the validator’s responsibility. The merged definition is always re-validated through [Prompt::new] regardless of strategy, so no construction invariant is weakened by the strategy choice.

Merge

Top-level key union for map-typed fields (variables, variants, metadata). Child-wins-whole-entry on key collision; no recursion (no deep). Scalar fields replace when overlay-present.

Replace

Wholesale field replacement (the default). Each overlay-present field replaces the base’s field; absent overlay fields are left untouched. Byte-identical to pre-017 behavior.

pub fn core_version() -> &'static str

Returns the version string of the underlying rendering kernel.

pub fn merge_definitions(base: Value, overlay: Value, strategy: MergeStrategy) -> Result<Value, ConsumerError>

The single-source map-union algorithm shared by both the typed [Prompt::derive_with] path and the Node binding’s derive_prompt. base and overlay must be serde_json::Value::Objects. Each top-level key in overlay is applied to base according to strategy: - [MergeStrategy::Replace]: each overlay-present key replaces the base’s key wholesale (the pre-017 behavior, preserved exactly). - [MergeStrategy::Merge]: for the three map-typed fields (variables, variants, metadata) the function unions top-level sub-keys with child-wins-whole-entry (no recursion). All other keys (scalar fields) replace wholesale, same as Replace. By operating entirely in serde_json::Value space this helper is the common denominator for both the typed Rust path (which serializes PromptDefinition + PromptOverlay to Value before calling this) and the Node binding (which is already JSON-native). This guarantees byte-identical results across bindings by construction — per-binding date/decimal serialization would otherwise let a typed-map union and a JSON-space union diverge. # Errors [ConsumerError::Load] if base or overlay is not a JSON object.

pub struct RenderResult

Render result + content-addressed provenance. Plain data returned to the caller — no telemetry sink, no tracing coupling. There is deliberately no vars_hash field.

pub guard: Option<String>

The guard instruction text, present only when guard expansion was opted in; None when the guard is disabled. Never concatenated into text.

pub name: String

The prompt name (def.name).

pub render_hash: String

Lowercase-hex SHA256(rendered text).

pub template_hash: String

Lowercase-hex SHA256(resolved variant source).

pub text: String

The rendered body text. The guard text is NEVER concatenated here.

pub variant: String

The resolved variant name (the reserved default, or the named arm).

pub struct GuardConfig

Per-render guard option. Opt-in, per render. When enabled is false: - The rendered body is byte-identical to a plain render (the pre-pass is not applied, no values are inspected, no entity-escaping occurs). - build_guard_text returns None. When enabled is true AND the definition declares at least one untrusted field (trusted: false): - The source pre-pass runs before rendering, rewriting each &#123;&#123; EXPR &#125;&#125; whose root identifier is untrusted into &#123;&#123; (EXPR) \| pp_guard_wrap &#125;&#125;. The pp_guard_wrap filter entity-escapes &, <, > (in that order) and wraps the result in <untrusted>…</untrusted>. Values of trusted roots are never touched. - build_guard_text returns a fixed advisory string referencing the markers. This is NOT a sanitizer. Enabling the guard makes untrusted values visually locatable in the output; it is not a guarantee that a downstream model will honour the markers. The advisory is a suggestion, not enforcement.

pub advisory: Option<String>

Optional override for the advisory sentence returned in [crate::RenderResult::guard]. None ⇒ [DEFAULT_GUARD_ADVISORY] (the fixed default that references the <untrusted>…</untrusted> markers). The <untrusted> MARKERS themselves are fixed and NOT configurable — they are the security-relevant contract. Only the human-readable advisory that explains them is overridable, for model-tuning or localization. A caller that overrides this owns its correctness (e.g. it should still describe the real markers). The override is plain text: it is never substituted, never parsed for placeholders, and never re-enters the template engine.

pub enabled: bool

When false, no delimiting and no guard advisory are produced.

pub struct CheckReport

The output of Prompt::check: an ordered list of [Finding]s. Empty ⇒ pass. Carries only findings — no rendered text, no mutated state.

pub fn is_empty(&self) -> bool

Alias for passed: true iff there are no findings.

pub fn passed(&self) -> bool

true iff there are no findings (the lint passed). Equivalent to self.findings.is_empty; reads more clearly at a CI gate call site.

pub findings: Vec<Finding>

Every advisory finding, in deterministic order. Empty ⇒ pass.

pub struct Finding

One actionable lint finding: it names the prompt, the variant where applicable, the failure kind, and a human-readable detail.

pub detail: String

A human-readable, actionable description. Carries no bound-value content.

pub kind: FindingKind

The kind of failure (the discriminant a consumer matches on).

pub prompt: String

The prompt’s name.

pub variant: Option<String>

The variant the finding pertains to (Some("default") / Some("<name>") for an agreement or analysis finding); None for a prompt-level trust/guard finding.

pub enum FindingKind

The closed set of lint-failure classes. UntrustedWithoutGuard is the only advisory class that Prompt::check can surface. All other hard invariants (undeclared variables, analysis errors, reserved variant names) are enforced at construction and are structurally unreachable from a live Prompt.

UntrustedWithoutGuard { field: String }

The prompt declares field as trusted: false but configures no guard for it (see the metadata.guard convention in module docs). The only advisory class surfaced by Prompt::check.

pub struct Composition

An explicit, ordered sequence of (Prompt, vars, variant) entries that resolves to a Vec<Message> in append order. Built with new + append; there is no fluent .chain. No Registry needed.

pub fn append<V>(&mut self, prompt: &Prompt, vars: &V, variant: Option<&str>) -> Result<(), ConsumerError>
where
V: Serialize + Validate,
<V as Validate>::Context: Default

Append one (prompt, vars, variant) entry, validating + serializing vars eagerly (see module docs). vars is validated once via garde now; on success it is serialized to the kernel’s value type ([minijinja::Value::from_serialize]) and the entry is stored (alongside a clone of prompt). On failure the garde report is normalized to [ConsumerError::Validation] and nothing is stored — the composition is unchanged, so a later resolve never sees a half-validated entry. Takes &mut self and returns Result<, ConsumerError> (not Self): the builder is intentionally not fluent/chainable. V::Context: Default so the whole-struct [Validate::validate] convenience applies (one validation pass over the entry’s entire input set). Context-carrying validation is intentionally out of v1 scope (one concrete path per concern). # Errors [ConsumerError::Validation] — garde rejected vars. The entry is not appended.

pub fn is_empty(&self) -> bool

true iff no entries have been appended.

pub fn len(&self) -> usize

The number of appended entries (== the resolved-message count on success).

pub fn new() -> Self

Create an empty composition. An empty composition resolves to Ok(vec![]).

pub fn resolve(&self) -> Result<Vec<Message>, ConsumerError>

Resolve the composition to an ordered Vec<Message>, rendering each entry — in append order — through the kernel. For each entry, in order: call [prompting_press_core::render] on the entry’s Prompt’s definition with the entry’s pre-validated value (vars were validated at append). The render result becomes Message &#123; role: <def.role stringified>, text: result.text &#125;. Composition uses no guard expansion — a default [GuardConfig] is passed (guard text is never concatenated into text). One entry’s render failure (unknown variant, strict-undefined reference, parse/render error) propagates as the normalized [ConsumerError]; the partial result built so far is discarded, never returned as success. An empty composition returns Ok(vec![]). resolve does not mutate self (it takes &self); it reuses the kernel’s render path rather than duplicating any rendering logic. # Errors [ConsumerError::Kernel] — the kernel rejected an entry’s render (unknown variant, strict-undefined reference, parse/render failure). Parse/Render detail is scrubbed.

pub struct Message

One resolved message in a composition’s output: a role-tagged rendered string. role is the prompt definition’s role stringified ("system" / "user" / "assistant"); text is that prompt rendered with the entry’s own validated vars.

pub role: String

The conversational role, taken from the prompt definition’s role.

pub text: String

The rendered body text for this entry.

pub struct FileSystemLoader

A loader that reads prompt files from a configured base directory. Maps a logical key to &#123;base&#125;/&#123;key&#125;&#123;suffix&#125; and returns the file’s raw text. ## Traversal guard Keys are treated as relative paths under base. The guard rejects: - keys with .. path components - absolute keys (start with / or a Windows drive prefix) - keys containing embedded NUL bytes - keys containing backslash (\\) or UNC path patterns - key="" or key="." (resolve to the base directory, not a file) - any intermediate "." component (e.g. foo/./bar) - symlinks that point outside the canonicalized base A canonicalize failure on a missing target returns load_not_found (not load_io). ## Read cap Reading a file exceeding max_bytes returns PromptLoadError::Io with code load_io.

pub fn new<impl Into<PathBuf>, impl Into<String>>(base: _, suffix: _, max_bytes: usize) -> Result<Self, PromptLoadError>

Construct a FileSystemLoader rooted at base with the given suffix and max_bytes. base is canonicalized at construction time. A non-existent base returns PromptLoadError::NotFound; other OS failures return PromptLoadError::Io. # Errors - [PromptLoadError::NotFound] if base does not exist. - [PromptLoadError::Io] on other canonicalize failures (e.g. permission error).

pub fn with_base<impl Into<PathBuf>>(base: _) -> Result<Self, PromptLoadError>

Convenience constructor with default suffix (.yaml) and DEFAULT_MAX_BYTES. # Errors - [PromptLoadError::NotFound] if base does not exist. - [PromptLoadError::Io] on other canonicalize failures.

pub max_bytes: usize

Maximum bytes to read from a file.

pub struct MemoryLoader

A loader backed by an in-memory key→text mapping. The primary use case is dependency injection in tests: production code uses a [FileSystemLoader] or a custom loader; tests substitute a MemoryLoader with hard-coded prompt text. No filesystem access is performed. A missing key returns [PromptLoadError::NotFound]; there is no I/O path.

pub fn new(map: HashMap<String, String>) -> Self

Construct a MemoryLoader from an existing key→text mapping.

pub enum PromptLoadError

The error type returned by [crate::loader::PromptLoader::load]. Distinct from [ConsumerError] at the type level. Normalizes into the common [&#123;field, code, message&#125;] contract under codes [code::LOAD_IO] and [code::LOAD_NOT_FOUND]. Messages are scrubbed: the logical key + code only; never file contents, full absolute paths, or secrets. ## Compatibility surface note PromptLoadError and its codes [code::LOAD_IO] / [code::LOAD_NOT_FOUND] are NEW additions to the closed error vocabulary (spec 019 compatibility-surface expansion). See the amendment in .specify/memory/DECISIONS.md.

Io { key: String, detail: String }

An I/O or resource failure occurred (read error, max_bytes exceeded, etc.). Code: [code::LOAD_IO].

NotFound { key: String }

A key was not found in the loader’s backing store (missing file, missing map entry). Code: [code::LOAD_NOT_FOUND].

pub trait PromptLoader

A pluggable source of raw prompt text. The single operation [PromptLoader::load] maps a logical key to the raw text of a prompt definition. The returned text is never parsed or validated — that belongs to the construct-from-text path ([crate::Prompt::from_yaml] etc.). Loading and construction are always separate, composable steps. The trait is object-safe (dyn PromptLoader is valid) and Send + Sync so loaders can be stored in shared state without additional wrappers. A blanket impl allows any Fn(&str) -> Result<String, PromptLoadError> closure to be used as a loader without defining a struct.

pub fn load(&self, key: &str) -> Result<String, PromptLoadError>

Load the prompt source for the given logical key, returning raw text. # Errors - [PromptLoadError::NotFound] — the key does not exist in the backing store. - [PromptLoadError::Io] — an I/O failure occurred, or a resource cap was exceeded.

pub struct FieldError

One normalized failure row — the common structured shape shared across every binding ([&#123;field, code, message&#125;]). - field: the offending field / path (a garde dot-path, a variable name, "variant", or "template"), or "" when no single field applies. - code: a stable string from the [code] vocabulary. - message: a human-readable, scrubbed description safe to log.

pub code: String

A stable code from the [code] vocabulary.

pub field: String

The offending field or path; "" when no single field applies.

pub message: String

A human-readable, scrubbed message safe to log.

pub enum ConsumerError

The single public error type for the consumer crate. Native error types ([garde::Report], [prompting_press_core::KernelError]) are mapped into this shape by the [From] impls in this module and never appear on a public signature. The enum is closed (no #[non_exhaustive]): the variant set is part of the compatibility surface.

Kernel(Vec<FieldError>)

The kernel rejected the render/source/analysis call. One [FieldError] per kernel failure, carrying the mapped code from the [code] vocabulary. Render detail is scrubbed; Parse detail is preserved (pre-binding template syntax).

Load(String)

Malformed input to the dual-input loader (bad YAML/JSON, or a deserialize error). Holds a short, loader-level description. Nothing is partially loaded.

Validation(Vec<FieldError>)

Typed-Vars validation failed (garde). One [FieldError] per reported path, each carrying [code::VALIDATION].

pub const EXCLUDED_FEATURE: &str = "excluded_feature";

The template used an excluded feature ([prompting_press_core::KernelError::ExcludedFeature]).

pub const LOAD: &str = "load";

Malformed YAML/JSON input, or a deserialize failure, in the dual-input loader.

pub const LOAD_IO: &str = "load_io";

A PromptLoader::load call failed due to an I/O error or an exceeded read cap (e.g. max_bytes exceeded).

pub const LOAD_NOT_FOUND: &str = "load_not_found";

A PromptLoader::load call could not find the requested key.

pub const PARSE: &str = "parse";

The template failed to parse ([prompting_press_core::KernelError::Parse]). The underlying detail is scrubbed.

pub const RENDER: &str = "render";

A render-time failure other than an undefined variable ([prompting_press_core::KernelError::Render]). The underlying detail is scrubbed.

pub const UNDEFINED_VARIABLE: &str = "undefined_variable";

A strict-undefined variable was hit at render time ([prompting_press_core::KernelError::UndefinedVariable]).

pub const UNKNOWN_VARIANT: &str = "unknown_variant";

The kernel was asked for a variant the definition does not declare ([prompting_press_core::KernelError::UnknownVariant]).

pub const VALIDATION: &str = "validation";

A garde validation failure (the consumer synthesizes this; garde exposes no machine code). One row per reported path.

docs current as of 0.5.0