08 — Schemas and data classification¶
Schemas in re-frame2 are Malli schemas attached to app-db paths. You register them with reg-app-schema (path-keyed, not id-keyed — the only reg-* that breaks that pattern, deliberately); the runtime validates app-db writes against the matching schemas in dev; production builds elide the validation at the call sites.
Schemas describe shape and validation. Per EP-0015, durable app-db data classification is not a schema concern: a schema must not be a second route to classify an app-db path the frame already owns. Where a schema is the owner's natural surface — a machine's :data, a resource's data/params, an HTTP response body's :decode slots — per-slot :sensitive? / :large? Malli props remain the one-and-only classification route for that owner's data. The full three-owner model (frame config for durable app-db; per-slot schema props for owner-local schema'd data; registration metadata for transient payloads) lives in Guide ch.23 — Privacy and large things.
This chapter covers the registration macros (rowed in 01 — Core, summarised here), the introspection surface in re-frame.schemas, the validator-extension seams (set-schema-validator! etc.), and the boundary-validation interceptor. For the canonical contracts, see 010-Schemas.md, 015-Data-Classification.md, and Privacy.md.
Registration¶
reg-app-schema¶
- Kind: macro
- Signature:
- Description: "Attach this Malli schema to this
app-dbpath." Path is the registration id — app-db schemas are path-keyed (the schemas-at-paths grain matchesget-in/assoc-in) and live in the schemas artefact's per-frame side-table (per rf2-cq1ak app-db schemas are NOT a registrar kind).(app-schema-at [:user])looks up by the same path vector. - Example:
- In the wild: 7GUIs
reg-app-schemas¶
- Kind: macro
- Signature:
- Description: Bulk plural form. Feature-modular apps registering 5–20 paths against the same prefix reach for this. Each entry routes through the singular form and is stamped with this call's source coords. Returns the vector of paths registered.
- Example:
- In the wild: realworld
The path-keyed-not-id-keyed asymmetry is principled. Paths are first-class in get-in / assoc-in / update-in; schemas-at-paths matches the dataflow grain; the lookup site (app-schema-at [:user]) reads the same way the write site ((assoc-in db [:user] ...)) reads. Spelling it as (reg-app-schema :user/schema schema) would have shifted the registration's id away from the dataflow grain.
See Conventions §reg-* return-value rule for the wider convention this row participates in.
Introspection¶
The introspection surfaces live in re-frame.schemas (artefact day8/re-frame2-schemas); consumers (:require [re-frame.schemas :as schemas]). They are not re-exported from re-frame.core — the registration macros live in re-frame.core and route through the schemas artefact at registration time, but the read-side surface stays in its own namespace.
app-schemas¶
- Kind: function
- Signature:
- Description: "Hand me every registered schema-at-path for this frame." Returns
{path schema}. Tools and agents walk this to enumerate the app's schema surface.
app-schema-at¶
- Kind: function
- Signature:
- Description: "Schema for this exact path." Returns the schema value or
nil.
app-schema-meta-at¶
- Kind: function
- Signature:
- Description: "Full registration-metadata map for this path." Returns
:path,:schema,:frame, plus source-coords (:ns/:line/:file) and the rest of:rf/registration-metadata. Pair tools and 10x reach for this when they need the registration anchor for click-back-to-code. The lighterapp-schema-atis the right call when only the schema value is needed.
app-schemas-digest¶
- Kind: function
- Signature:
- Description: "Single hash over the frame's whole schema surface." Used by SSR hydration compatibility checks and by tools that want to know "has the schema corpus changed?" without diffing schema-by-schema.
Validator-extension seams¶
The default validator ships Malli's validate / explain pair. These seams let apps swap in their own validator — typically to drop the Malli dep entirely, or to add a custom explainer that formats failures for the app's domain.
set-schema-validator!¶
- Kind: function
- Signature:
- Description: "Install the validator the framework uses at every dev-time schema-validation site." Swaps ONLY the validator;
nildisables validation entirely. The default ships Malli's pair; this seam is for apps that want to swap to a different validator without forking the framework. To install the validator/explainer/printer together, useset-schema-fns!.
set-schema-fns!¶
- Kind: function
- Signature:
- Description: "Atomically install any subset of the validator / explainer / printer bundle from a single map." The honest bundle setter — named for what it sets, not just the validator. Each key is optional; an absent key leaves the existing registration in place, and a
nil:printcoerces to the default EDN canonicaliser. The one-call substitute-Malli boot pattern, so the three fns never drift mid-boot.
set-schema-explainer!¶
- Kind: function
- Signature:
- Description: "Install the explainer the framework uses to enrich
:rf.error/schema-validation-failuretraces':explainkey." Companion toset-schema-validator!.
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. Parallel to the validator / explainer setters: non-Malli ports register their own serialiser so cross-runtime digest comparison reflects their port's contract.
The three setters answer three different questions: validation correctness (validator), human-readable failure messages (explainer), and stable canonical printing for digest (printer). Most apps use the defaults; ports and apps swap them selectively.
The boundary interceptor¶
validate-at-boundary-interceptor¶
- Kind: Var (interceptor value)
- Signature:
- Description: A pre-built interceptor value, not a fn (interceptor
:idis:rf.schema/at-boundary). Add it to areg-eventmetadata map's:interceptorsvector for production-boundary validation. Do not call it as a fn — it has no fn arity; invoking(rf/validate-at-boundary-interceptor ...)raisesArityException.
(rf/reg-event ::receive-from-server
{:interceptors [rf/validate-at-boundary-interceptor]}
(fn [{:keys [db]} [_ payload]] {:db (assoc db :data payload)}))
The pattern: dev-time validation runs at every commit by default; production-time validation runs only at handlers wearing validate-at-boundary-interceptor. Use it on handlers that ingest data from outside the app's trust boundary (HTTP replies, websocket frames, postMessage handlers).
Data classification¶
Schemas describe shape; classification of durable app-db data is frame-owned, not schema-attached. Per EP-0015, you declare sensitive/large app-db paths on the frame at creation (reg-frame / make-frame), and the framework's centralized projection enforces them at every wire boundary:
(rf/reg-frame :app/main
{:sensitive {:app-db [[:auth :token]
[:tenant :partner-api-key]]
:http {:headers ["X-Honeycomb-Team"]}}
:large {:app-db [[:documents :csv-upload]]}})
There is no schema-attached or imperative-mark route to classify the same app-db path; the frame owns it, full stop. (Schema :sensitive? / :large? props remain the route for owner-local schema'd data — machine :data, resource data/params, HTTP response bodies — see 04 — Machines, 16 — Resources, 07 — HTTP.) Transient payloads (event args, sub/flow outputs) are classified by :sensitive / :large metadata on the registration that introduces the shape.
The full teaching of the three owners, the two projection primitives, and the egress profiles lives in Guide ch.23 — Privacy and large things. For the framework-internal egress primitives (project-egress, elide-wire-value) consumed by tools and sinks, see 11 — Instrumentation.
Composition rule¶
When both classifications match the same slot (:sensitive? AND :large?), sensitive drop wins — the size marker is suppressed because it would leak :path / :bytes / :digest information from a sensitive slot. The composition rule is normative; per 009 §Size elision in traces and 015 §Projection.
See also¶
- 01 — Core —
reg-app-schema/reg-app-schemasrowed in registration. - 03 — Effects and interceptors —
validate-at-boundary-interceptorrowed in the interceptor table. - 11 — Instrumentation —
project-egress/elide-wire-valueand the trace-surface privacy posture. - Spec 010 — Schemas, 015-Data-Classification.md, Privacy.md, Security.md.