re-frame.schemas¶
Schema attachment, validation, and data-classification. Schemas are Malli schemas attached to app-db paths via reg-app-schema. In dev builds the runtime validates every app-db write against the matching schemas. It also validates event, fx, and subscription values that carry a :schema. A mismatch emits an :rf.error/schema-validation-failure trace. Production builds elide validation at the call sites.
This namespace owns four surfaces:
- the read-side introspection functions
- the pluggable validator / explainer / printer seams
- the per-slot
:sensitive?/:large?schema walkers that drive failure-trace redaction - the test-support snapshot / restore / clear hooks
The registration macros reg-app-schema / reg-app-schemas are surfaced through the re-frame.core facade (called as rf/reg-app-schema). Their full contract lives here.
Ships in artefact day8/re-frame2-schemas. Most app code touches only Registration and Introspection; the remaining sections are framework-integration and advanced surfaces. See Validate with schemas for the working guide.
Registration¶
The registration macros live in re-frame.core and route through the schemas artefact at registration time. Consumers call them as rf/reg-app-schema / rf/reg-app-schemas. re-frame.core.md lists them briefly; the full contract is here. re-frame.schemas also exports both names as plain functions for programmatic (HoF) registration. The functions carry the same contract but do not capture call-site source coords.
reg-app-schema¶
- Kind: macro
- Signature:
- Description: Attach a Malli schema to an
app-dbpath. - The schema is the positional value slot (rf2-qm7k83 Part A), uniform with the rest of the
reg-*family. The optional middle metadata map carries the frame target under:frame(a frame-id keyword or a frame value), plus:docand open:my/*keys. - The path is the registration id. App-db schemas are path-keyed and live in the schemas artefact's per-frame side-table; they are NOT a registrar kind.
(app-schema-at [:user])looks up by the same path vector. pathis a sequentialget-inpath of concrete segments, normalized to canonical vector form.[]registers a whole-app-dbroot schema. Returns the normalized path.- Raises:
:rf.error/app-schema-bad-metadata— the middle metadata arg in the 3-slot form is not a map.:rf.error/app-schema-bad-path— a non-sequential path, or a non-concrete segment.:rf.error/app-schema-runtime-path— the first segment reaches the runtime-db partition (:rf.runtime/*,:rf.db/runtime, or the legacy:rf/runtimeroot).:rf.error/app-schemas-bad-arg— a:frametarget that does not resolve to a keyword frame id.:rf.error/no-frame-context— no:frameand no established frame scope.
- Dev-only diagnostics: re-registering a schema at a path whose live
app-dbvalue fails the new schema emits an:rf.schema/violationwarning trace. Registering an opaque compiled schema the per-slot walker cannot introspect warns once per process with:rf.warning/schema-walker-opaque. - Example:
reg-app-schemas¶
- Kind: macro
- Signature:
- Description: Bulk plural form of
reg-app-schema. - Each entry routes through the singular form and is stamped with this call's source coords.
- The optional second arg names the frame for every entry: an opts map (
{:frame target}), a bare frame-id keyword, or a frame value. One frame per call. - Returns the vector of paths registered, in map-iteration order.
{}is the documented empty no-op. - Every path is shape-checked before any store mutation, so a batch containing one invalid path (
:rf.error/app-schema-bad-path/:rf.error/app-schema-runtime-path) registers nothing. - A
nil/ non-map first arg raises:rf.error/app-schemas-bad-batch. - Example:
See re-frame.core.md for the reg-* return-value convention this registration participates in.
Introspection¶
The introspection surfaces live in re-frame.schemas and are not re-exported from re-frame.core. Every opts-or-frame-id argument below accepts an opts map ({:frame target}), a bare frame-id keyword, or a frame value (normalized to its frame id). A malformed argument raises :rf.error/app-schemas-bad-arg. Omitting the frame outside any frame scope raises :rf.error/no-frame-context.
app-schemas¶
- Kind: function
- Signature:
- Description: Return every registered schema-at-path for the frame as
{path schema}. - Example:
app-schema-at¶
- Kind: function
- Signature:
- Description: Return the schema registered at an exact path, or
nil. - Example:
app-schema-meta-at¶
- Kind: function
- Signature:
- Description: Return the full registration-metadata map for a path, or
nilwhen nothing is registered. - Contains
:path,:schema,:frame, source-coords (:ns/:line/:file), and the rest of:rf/registration-metadata. - Use
app-schema-atwhen only the schema value is needed. - Example:
app-schemas-digest¶
- Kind: function
- Signature:
- Description: Return a single hash over the frame's whole schema surface.
- Canonical wire form:
"sha256:"followed by the first 16 lowercase hex chars. - Byte-deterministic across runtimes. The empty schema set produces a stable digest.
- SSR hydration compatibility checks use it. So do tools that want to know whether the schema corpus changed, without diffing schema-by-schema.
- Example:
frame-schema-entries¶
- Kind: function
- Signature:
- Description: Return the full
{path schema-meta}map for a frame, or{}. - The lower-level cross-artefact read seam.
re-frame.elision,re-frame.epoch, and thevalidate-app-schema!loop consume it. app-schemasprojects each entry down to{path schema}; this returns the whole per-path metadata map (:schema,:path,:frame, source-coords).
Validation entry points¶
The four dev-time validate-*! functions are the locked validation sites the framework calls for you:
- pre-handler (event)
- pre-fx
- post-commit (
app-db) - post-recompute (subscription)
They are public and manifested — tools and conformance tests call them directly. Ordinary app code does not: you register a :schema and the runtime invokes these. Every body lives inside an interop/debug-enabled? gate, so the whole surface is dead-code-eliminated in production. Each fn then returns true.
On every surface, a structurally malformed registered schema (one that makes the registered validator throw) emits the distinct :rf.error/malformed-schema trace and returns false (fail closed), never a silent pass. That trace carries structural locator slots only — no value-bearing slots.
validate-app-schema!¶
- Kind: function
- Signature:
- Description: After a handler commits
:db, walk every registered app-schema for the named frame and validate the post-commitapp-db. Only the named frame's schemas are walked. - Failures emit
:rf.error/schema-validation-failure(one trace per failing entry) with the registered explainer's output attached. Value-bearing slots are redacted when the failing slot is:sensitive?. - A malformed entry (the validator throws) emits
:rf.error/malformed-schemafor that entry, contributesfalse, and does not stop sibling schemas from validating. event-id(optional) names the handler whose commit prompted the failure — surfaced as:failing-id.- Returns
truewhen every registered schema conformed. Also returnstruewhen no validator is registered, no schemas are registered for the frame, or the build elided validation. - Returns
falsewhen at least one schema failed. The router consumes afalseto roll the:dbeffect back to the pre-handler value. - A hard no-op for every schema when
set-schema-validator!has been called withnil.
validate-event!¶
- Kind: function
- Signature:
- Description: Before an event handler runs, validate the event vector against any
:schemaon the handler's registration metadata. - On failure, emits
:rf.error/schema-validation-failure :where :event. The caller skips the handler (recovery:no-recovery). - A
:sensitive?slot anywhere in the event schema redacts the value-bearing trace slots. The common case is a:cat/:catnpayload slot, such as a:passwordslot in a login schema. - The optional
framearg stamps a:frametag so the violation is captured into the in-flight epoch's:trace-events(read by the Xray Issues / Schema-timeline lens). The runtime passes it; direct callers may omit it. - Returns
true/false.
validate-fx!¶
- Kind: function
- Signature:
- Description: Before an fx handler runs, validate its args against any
:schemaon the fx's registration metadata. - On failure, emits
:rf.error/schema-validation-failure :where :fx-args. Only the offending fx is skipped (recovery:skipped). Sibling fx in the same:fxvector still run, and downstream queued events still drain. - The optional
framearg stamps a:frametag for epoch capture. - Returns
true/false.
validate-sub!¶
- Kind: function
- Signature:
- Description: After a subscription recomputes, validate its return value against any
:schemaon the sub's registration metadata. - On failure, emits
:rf.error/schema-validation-failure :where :sub-return. The caller replaces the value with the default (recovery:replaced-with-default). - The optional
framearg stamps a:frametag for epoch capture. - Returns
true/false.
Boundary validation and redaction seams¶
These three functions are the production-side and cross-artefact seams. validate-with-registered-fn / explain-with-registered-fn are the pure check surface used by the production boundary-validation interceptor (validate-at-boundary-interceptor, in re-frame.core.md), which reaches them through the late-bind table. redact-validation-tags is the one schema-aware redactor; every validation-failure emit site outside this namespace routes through it.
validate-with-registered-fn¶
- Kind: function
- Signature:
- Description: Apply the registered validator to
(schema, value). This is the public check seam the boundary-validation interceptor uses. It runs in production, outside thedebug-enabled?gate thevalidate-*!hot path sits behind. - Returns
trueon conform. - Returns
falseon fail, including when a structurally malformed schema makes the validator throw (fail closed). - Returns
truewhen no validator is registered. No-validator means no-validation, mirroring the hot path. - Does NOT emit a trace (the interceptor owns the failure envelope) and does NOT consult
debug-enabled?.
explain-with-registered-fn¶
- Kind: function
- Signature:
- Description: Apply the registered explainer to
(schema, value). Companion tovalidate-with-registered-fnfor the boundary interceptor. - Returns the explanation map / data on fail.
- Returns
nilwhen the value conforms, when no explainer is registered, or when the explainer throws. A throwing explainer degrades tonilbecause diagnostics must never change the verdict.
redact-validation-tags¶
- Kind: function
- Signature:
- Description: The shared schema-aware redaction seam for every validation-failure trace emitted OUTSIDE this namespace. Its callers are the production boundary interceptor, machine
:datavalidation, the:sub-overridepath, flow-output validation, and the recordable-coeffect:rf.error/cofx-value-invalidemit. Given theschemathe failing value was checked against and a failure-tracetagsmap, it returns the tags with: - a
:sensitive?schema → the value-bearing slots (:value/:received/:explain/:explain-humanized/:rf.fx/args/:rf.sub/query-v) scrubbed to:rf/redacted, and:sensitive? truestamped. An opaque compiled schema the walker cannot introspect fails closed and is treated as sensitive. - a
:large?(non-sensitive) schema → those same slots elided to the:rf.size/large-elidedmarker. - otherwise → the tags ride back verbatim.
- Off-namespace callers reach it through the
:schemas/redact-validation-tagslate-bind hook. When the schemas artefact is absent, the tags fall through verbatim. Idempotent.
Validator extension seams¶
The default validator ships Malli's validate / explain pair (plus an EDN canonical printer for the digest). These seams let an app swap in its own validator. The three setters answer three different questions: validation correctness (validator), human-readable failure messages (explainer), and stable canonical printing for the digest (printer).
set-schema-validator!¶
- Kind: function
- Signature:
- Description: Install the validator the framework uses at every dev-time schema-validation site.
validate-fnis(fn [schema value] truthy?)— themalli.core/validateshape.nildisables validation entirely.- Swaps ONLY the validator. Last-write-wins; returns the installed validator (may be
nil). - To install the validator/explainer/printer together, use
set-schema-fns!. - Example:
set-schema-fns!¶
- Kind: function
- Signature:
- Description: Atomically install any subset of the validator / explainer / printer bundle from a single map.
- Each key is optional; an absent key leaves the existing registration in place.
- A
nil:printcoerces to the default EDN canonicaliser. Anil:validateor:explaindisables that fn. - Last-write-wins per key. Returns the installed bundle map
{:validate … :explain … :print …}, reflecting the live state of all three fns after the call. - This is the one-call substitute-Malli boot pattern: the three fns never drift mid-boot.
- Example:
set-schema-explainer!¶
- Kind: function
- Signature:
- Description: Install the explainer the framework uses to enrich
:rf.error/schema-validation-failuretraces':explainkey. explain-fnis(fn [schema value] explanation)— themalli.core/explainshape.nildisables explanations (the failure trace omits:explain).- Last-write-wins; returns the installed explainer (may be
nil). Companion toset-schema-validator!. - Example:
set-schema-printer!¶
- Kind: function
- Signature:
- Description: Install the schema-print companion the digest pipeline hashes.
(fn [schema-value] canonical-string). Must be pure and deterministic across runtimes.nilfalls back to the default EDN canonicaliser, so the digest is never undefined.- Last-write-wins; returns the installed printer (the default when
nilwas passed). Parallel to the validator / explainer setters. - Example:
reset-schema-validator!¶
- Kind: function
- Signature:
- Description: Reset the validator, explainer, and printer back to the framework Malli defaults. Test-support helper — restores the defaults after a test that swapped them via
set-schema-validator!/set-schema-explainer!/set-schema-printer!.
snapshot-schema-fns¶
- Kind: function
- Signature:
- Description: Capture the currently-installed validator / explainer / printer bundle as one value, in the same shape
set-schema-fns!accepts and returns. - The bundle-level companion to
snapshot-schemas-by-frame. The captured value round-trips losslessly throughrestore-schema-fns!. - Unlike
reset-schema-validator!, which restores the framework default, this captures whatever custom bundle is currently installed. A test can therefore restore the prior custom bundle. :validate/:explainmay benil;:printis nevernil.
restore-schema-fns!¶
- Kind: function
- Signature:
- Description: Reinstall a validator / explainer / printer bundle captured by
snapshot-schema-fns. This is a full install of all three. It routes throughset-schema-fns!, so anil:printin the bundle coerces to the default printer — the printer-never-nil invariant holds. Returns the installed bundle map.
Schema classification walkers¶
The pure-data per-slot flag extractors and predicates. They walk a Malli vector-form EDN schema and report which slots carry :sensitive? true or :large? true per-slot props.
Two kinds of consumers read them: the schema-validation-failure-trace redactor, and the owner-local schema-prop consumers — a machine's :data-schema, a resource's data/params schema, the HTTP body-privacy projector, and story-mcp's tool-egress projector.
They describe shape, not durable app-db egress policy. Durable app-db classification is event-owned: a reg-event returns :sensitive / :large alongside :db. A compiled / opaque m/schema value is treated as an opaque leaf, so register the vector form when per-slot flags need to be visible.
extract-large-paths-from-schema¶
- Kind: function
- Signature:
- Description: Walk a Malli schema EDN form at
base-pathand return a{path declaration}map for every:large? trueslot found. - Each declaration carries
:source :schema, plus:hintpropagated verbatim when the slot props carry one. - This is the pure-data
:large?extractor the owner-local size-elision consumers read directly. It does NOT feed theapp-dbegress registry — that registry is frame-owned. - Example:
extract-sensitive-paths-from-schema¶
- Kind: function
- Signature:
- Description: As
extract-large-paths-from-schema, for the:sensitive? trueper-slot flag. Drives the validation-failure-trace redactor's decision about which value-bearing slots to scrub. Memoised by(schema, base-path). Clear the memo for test isolation viaclear-sensitive-paths-cache!.
schema-has-sensitive?¶
- Kind: function
- Signature:
- Description:
truewhen the schema declares ANY slot sensitive. Two cases count: the schema's container-level props carry:sensitive? true, or a nested:sensitive? trueslot lives anywhere inside it. This is the conservative whole-schema check: when any slot is sensitive, the whole trace's value-bearing slots are redacted. - Example:
schema-has-large?¶
- Kind: function
- Signature:
- Description:
truewhen the schema declares ANY slot:large? true. The mirror ofschema-has-sensitive?on the other per-slot flag. Whentrue, the validation emit-site substitutes the:rf.size/large-elidedsize marker for the value-bearing slots. When a slot is also sensitive, sensitive wins.
schema-sensitive-at?¶
- Kind: function
- Signature:
- Description: Path-targeted sensitivity check.
truewhen the slot atin-pathis sensitive. Two cases count: an ancestor along the path is:sensitive?(the failing slot sits under a sensitive container), or a descendant of the slot is:sensitive?(the slot's value carries a sensitive child). in-pathis the value-relative path Malli reports as:in. Anilor emptyin-pathis equivalent toschema-has-sensitive?.- This is the leaf-precise check the
app-dbhot path uses for its narrowed:valueslot. A non-sensitive failing leaf whose sibling is sensitive is therefore not over-redacted.
schema-opaque?¶
- Kind: function
- Signature:
- Description:
truewhenschemais a compiled / opaque value the pure-data walker cannot introspect for per-slot flags. That means any non-vector, non-keyword form: a compiledmalli.core/schemaobject, a map, a fn. A bare keyword (:int,:string, a registry ref) is NOT opaque. The redaction path fails closed on an opaque schema — it redacts as if sensitive, since Malli may honour a:sensitive?slot the walker cannot see. The supported way to make per-slot flags visible is registering the vector form.
Test-support¶
The per-frame registry and diagnostic-latch maintenance hooks. re-frame.test-support's reset-runtime fixture drives the snapshot / restore / clear trio through late-bind hooks. The clear-* latch resetters and cache clearers let a test start each case from a clean diagnostic + cache slate. on-frame-destroyed! is the framework's own frame-teardown cleanup.
snapshot-schemas-by-frame¶
- Kind: function
- Signature:
- Description: Return a snapshot value of the per-frame schema registry. The registry-level companion to
snapshot-schema-fns; restore it withrestore-schemas-by-frame!.
restore-schemas-by-frame!¶
- Kind: function
- Signature:
- Description: Reset the per-frame schema registry to a snapshot taken by
snapshot-schemas-by-frame.
clear-schemas-by-frame!¶
- Kind: function
- Signature:
- Description: Reset the per-frame schema registry to
{}. Used by test fixtures and by the reset-runtime fixture's:clear-app-schemas? truepath. The per-frame registry is the schemas artefact's only mutable registration state.
on-frame-destroyed!¶
- Kind: function
- Signature:
- Description: Drop every schema registered against a destroyed frame so a subsequent
make-frameof the same id starts with a clean schema slate. Called from frame teardown through the:schemas/on-frame-destroyed!late-bind hook. Idempotent — a missing frame entry is a no-op.
clear-validator-unavailable-warned!¶
- Kind: function
- Signature:
- Description: Reset the once-per-process
:rf.warning/schema-validator-unavailablediagnostic latch so each test case starts from a clean slate.
clear-walker-opaque-warned!¶
- Kind: function
- Signature:
- Description: Reset the once-per-process
:rf.warning/schema-walker-opaquediagnostic latch — the nudge emitted when a schema is registered as an opaque compiled value the walker cannot introspect.
clear-edn-print-cache!¶
- Kind: function
- Signature:
- Description: Reset the
app-schemas-digestprinter memo. Returnsnil. - Test-support only. The memo is process-lifetime and bounded by the registered-schema cardinality (schemas register once at boot), so production never needs this.
- A test that registers many distinct fresh schemas clears it in fixture teardown so the cache doesn't grow unbounded across the suite.
clear-sensitive-paths-cache!¶
- Kind: function
- Signature:
- Description: Reset the
extract-sensitive-paths-from-schemawalker memo. Test-support companion toclear-edn-print-cache!; same bounded-cache rationale. Returnsnil.
See also¶
- re-frame.core.md — the
reg-app-schema/reg-app-schemasfacade rows, thevalidate-at-boundary-interceptorinterceptor value, and the commit-plane data-classification effects (:sensitive/:large/:clear-sensitive/:clear-large) that own durableapp-dbclassification. - Validate with schemas — the working guide to schemas at
app-dbpaths. - Keep secrets out of traces — data classification,
:sensitive?, and large values.