Spec 001 — Registration¶
Status: Drafting. v1-required. The metadata-map shape that every
reg-*registration accepts. This Spec is the single-source for the registration grammar and the registry kind taxonomy. State machines themselves register under:event— a machine is an event handler whose body comes fromcreate-machine-handler(per 005).
Abstract¶
Every kind of thing in re-frame2 — events, subs, fx, cofx, views, frames, schemas, routes — is registered with metadata. The metadata is a small, open map that consumers (the runtime, tools, AIs) read. The pattern's commitment: every registered entity carries enough metadata for an agent to find, understand, and reason about it without source-code spelunking.
This Spec defines:
- The shape of the metadata map.
- The required keys, the optional keys, and the conventions for additions.
- The mechanism for capturing source coordinates.
- The query API tools and AIs use to inspect registrations.
- The relationship between the metadata, schemas (Spec 010), and the trace stream (Spec 009).
Canonical ownership boundaries¶
001 is the single canonical owner for registration. Three ownership boundaries that touch other Specs:
- Machine guards and actions are NOT registry entries. They are machine-local declarations inside each
create-machine-handlerspec's:guards/:actionsmaps; transition-table keyword references resolve machine-locally at registration time. There is no:machine-guard/:machine-actionregistry kind. See 005 §Registration — the machine IS the event handler. The machine's event-handler registration (which carries:rf/machine? truein metadata) is an ordinary:evententry covered by this Spec. - Metadata-map shape lives here; the formal Malli schema lives in Spec-Schemas. 001 owns the metadata-map's required-vs-optional key prose, semantics, and per-kind extensions. The corresponding
:rf/registration-metadataMalli schema (and per-kind refinements) lives in Spec-Schemas. Ports that don't ship a runtime schema layer follow this Spec's prose without needing the Malli artefact. - Registrar query API is owned here. 001 specifies the contract — function signatures, return shapes,
kindkeyword set, JVM-runnability. 002 §The public registrar query API re-tabulates the surface alongside frame-runtime queries (get-frame-db,snapshot-of,sub-topology,sub-cache) for tooling-side discoverability; that table is a cross-reference, not a competing source. Where 001 and 002 disagree, 001 wins for registry-query semantics; 002 wins for runtime-state queries that aren't in the registrar (sub-cache, frameapp-dbaccess).
Registration grammar¶
Every registration takes the same shape:
The id is an instance of the identity primitive.
The metadata map is open (consumers tolerate unknown keys; new keys are added additively per Spec-ulation). The standard keys are:
| Key | Type | Required? | Meaning |
|---|---|---|---|
:doc |
string | SHOULD (dev-warned) | One-sentence description of what this registration does. Surfaced in tooling, agent inspection, error messages. Absent on a reg-* call, the dev-build runtime emits :rf.warning/missing-doc once per (kind, id) pair (per §:doc is dev-warned when absent, below). The key is not structurally required — the registration succeeds without it; the warning is the nudge, not a gate. Production builds elide the check entirely. |
:spec |
schema | optional | Shape description for the registration's input or output. In dynamic hosts, a Malli/Pydantic/Zod schema; in static hosts, the host's type system. (See 010 for CLJS-specific Malli usage.) |
:ns |
symbol | auto-supplied | Source namespace where the registration occurred. Captured by the macro at compile time (CLJS reference). |
:line |
integer | auto-supplied | Source line. |
:file |
string | auto-supplied | Source file. |
:tags |
set of ids | optional | Application-defined tags for filtering (e.g., #{:critical :auth}). |
:platforms |
set of platform-ids | optional | Where the registration is allowed to run. Set of :client, :server, etc. (See 011.) |
For reg-event-*, the interceptor chain is positional (a separate vector argument between the metadata-map and the handler), NOT a metadata-map key. See §Allowed forms of the middle slot below and Conventions §:interceptors is positional, not metadata for the rationale and the warning the runtime emits when :interceptors is mis-placed inside the metadata-map.
Return value¶
Every reg-* returns its primary id — the keyword (or path, for reg-app-schema) the caller registered with. The contract is uniform across the family per Conventions §reg-* return-value convention. Per-kind surfaces (Specs 002 / 004 / 005 / 010 / 011 / 012 / 013) inherit this without restating it.
Per-kind extensions (e.g., :on-create on reg-frame, :path on reg-route) are documented in their respective Specs. Notably reg-frame's metadata-map does recognise :interceptors (frames have no positional middle slot — per Spec 002 §:interceptors).
Allowed forms of the middle slot¶
The middle slot of reg-event-* is either:
-
A vector of interceptors (the interceptor chain):
-
A metadata map (reflection metadata only —
:doc,:spec,:tags, ...): -
Both — metadata map AND a positional interceptors vector (the canonical form when an event has both reflection metadata and an interceptor chain):
The function discriminates on the type of each argument: a map is metadata, a vector is interceptors. :interceptors inside the metadata-map is not a valid position — the runtime emits :rf.warning/interceptors-in-metadata-map at registration time and the chain is silently ignored. (Form 2 in earlier drafts of this Spec accepted :interceptors inside the metadata-map; that path was removed per rf2-bbea — one canonical position is simpler than two.)
For reg-sub, reg-fx, reg-cofx, reg-frame, reg-app-schema, etc., the middle-slot is the metadata map only — there's no legacy vector form to compete with. reg-view is the only registration that ships as a macro (defn-shape — auto-defs the symbol, auto-derives the id, auto-injects dispatch / subscribe lexically); the plain-fn surface for runtime / programmatic registration is reg-view*. See Cross-Spec-Interactions §21 Family asymmetry for why the family is asymmetric.
Registry model — the canonical kind keyword set¶
The registrar is a (kind, id) → metadata map. The kind keyword identifies which registration function fed the entry, and is the argument to the query API (§The query API, below):
kind |
What it covers | Registration function(s) |
|---|---|---|
:event |
Every event handler regardless of arity | reg-event-db, reg-event-fx, reg-event-ctx |
:sub |
All subscriptions | reg-sub |
:fx |
Registered effect handlers | reg-fx |
:cofx |
Coeffect handlers | reg-cofx |
:view |
Registered views | reg-view |
:frame |
Registered frames | reg-frame (also make-frame) |
:route |
Routes | reg-route (Spec 012) |
:app-schema |
App-db schemas registered at paths — RESERVED kind; the registrar slot is intentionally empty (the schemas artefact owns its own per-frame side-table). Tools introspect via schemas/app-schemas / schemas/app-schema-meta-at rather than handlers :app-schema. |
reg-app-schema (Spec 010) |
:head |
Registered SSR head/meta functions (per 011 §Head/meta contract) | reg-head (Spec 011) |
:error-projector |
Registered SSR error projectors (internal trace event → public-error shape, per 011 §Server error projection) | reg-error-projector (Spec 011) |
:flow |
Registered flows (computed-state declarations materialised into app-db, per 013 §The registration shape) |
reg-flow (Spec 013) |
:event is a single kind even though three registration functions feed it (reg-event-db, reg-event-fx, reg-event-ctx). The arity-distinguishing internal sub-kind is on the metadata as :event/kind ∈ {:db :fx :ctx} — (rf/handlers :event) returns every event handler regardless of which reg-event-* registered it; tools that need the sub-kind read it from metadata.
Machine guards and actions are NOT a registry kind — they are machine-scoped, declared in each machine's
:guards/:actionsmaps inside thecreate-machine-handlerspec. See 005 §Registration.Downstream tools needing kind-shaped registration own their own side-tables. The framework registrar's
kindsset stays closed; tools like Story (tools/story/) maintain their own internal registries (e.g.tools.story.registry/*) and expose query surfaces via bridge fns. The closed-kinds discipline keeps the framework boundary stable; per-tool side-tables stay scoped to the tool that owns them.
The handler function¶
A named function is preferred.
(rf/reg-event-fx :cart.item/remove
{:doc "Remove an item from the cart by id."
:spec [:cat [:= :cart.item/remove] :uuid]}
(fn handler-cart-item-remove [{:keys [db]} [_ id]]
{:db (update-in db [:cart :items] (fn [items] (vec (remove #(= id (:id %)) items))))}))
The handler's name shows up in stack traces, the trace stream, tools that walk the registry, and AI inspection results. Anonymous handlers (fn without a name) work but are second-best.
Source-coordinate capture (CLJS reference)¶
The CLJS reference uses macros to capture :ns / :line / :column / :file at compile time. The four keys are the canonical source-coord shape — :rf/source-coord-meta per Spec-Schemas. :column is captured wherever the host's compile-time form metadata exposes it (CLJS's &form/&env does); ports whose macro layer has no column information omit the key. Per Tool-Pair §Source-mapping and 006 §Source-coord annotation:
(defmacro reg-event-db
[id & args]
(let [[metadata handler] (resolve-args args)
coords {:ns &env :line ... :column ... :file ...}]
`(re-frame.core/-reg-event-db
~id
~(merge coords metadata)
~handler)))
:line and :column come from (meta &form); :ns and :file come from the compile-time *ns* / *file*. The captured map is merged into the registration metadata and surfaces on (rf/handler-meta kind id) returns. The companion DOM-attribute contraction emitted by view substrates (<ns>:<sym>:<line>:<col>) is :rf/source-coord-attr per Spec-Schemas and Spec 006 §Attribute value format; :file is not part of the attribute string — consumers recover it via (rf/handler-meta :view <handler-id>).
This is a CLJS-implementation choice. Other in-scope JS-cross-compile language ports (per 000 §The pattern):
- TypeScript: stack-frame inspection at registration time, or build-time codegen from a registry-discovery pass. Macros aren't available at the source level.
- Squint: macros run at compile time on the Squint side; the CLJS-style approach transfers directly.
- Melange / ReScript / Reason: PPX (
bs.line,bs.file) captures source coords at compile time; the resulting JS carries the captured strings as constants. - Fable (F#) / Scala.js / PureScript / Kotlin/JS: compile-time source-position primitives in each source language (
__SOURCE_FILE__/__LINE__in F#;sourcecode.Compatmacros in Scala;Type.SourcePos-style helpers in PureScript;kotlin.reflect/ annotation processing in Kotlin) — surface the coords through a small wrapper around the host's React binding'sreg-*equivalent.
Source coords are valuable for navigation (jump-to-source from a tool, navigate-from-error, AI-source-cite) but their absence does not violate conformance. Per Principles.md §Optional capabilities, source-coord capture is opt-in.
The query API¶
The registry is a public, queryable structure. Tools and agents read it without private-API spelunking. The CLJS reference exposes:
| Function | Returns |
|---|---|
(rf/handlers kind) |
All registrations for a kind. Returns id → metadata. |
(rf/handlers kind pred-fn) |
Filtered: only registrations where (pred-fn metadata) returns truthy. |
(rf/handler-meta kind id) |
A single registration's metadata. Returns nil if not registered. |
(rf/frame-ids) |
All registered frame ids. |
(rf/frame-meta frame-id) |
Metadata for a specific frame. |
The valid kind values are defined in §Registry model above.
A handler's metadata exposes its sub-kind:
Per 002-Frames §The public registrar query API, these queries are stable, public, and JVM-runnable.
(re-frame.core/view id) — runtime-lookup handle for registered views¶
(re-frame.core/view id) is the runtime-lookup handle for a view registered under id. It returns the registered render fn (whatever shape — Form-1, Form-2 — produced by reg-view or reg-view*) or nil if no view is registered under that id.
(rf/reg-view counter [label] [:button label])
(rf/view :my.ns/counter) ;; → render fn
(rf/view :nope) ;; → nil
view is the canonical lookup handle because the registry is id-keyed while render trees consume Vars (see Spec 004 §Calling a registered view and Conventions §Render-tree shape vs runtime lookup). The lookup is the bridge for callers that hold an id but no Var — typically reg-view* registrations (where there is no auto-defed Var) and tools/devtools that walk the registry by id. Returning nil for an unregistered id is a normal lookup miss (no error trace).
Hot-reload semantics¶
Re-registering the same id replaces the previous handler. This is intentional — figwheel/shadow-cljs save→re-eval is the canonical CLJS dev-loop, and re-registration is part of how that loop works. The system stays live during the reload window: dispatch is not paused, in-flight events finish, the user's app-db survives, the page does not blink.
v1 reference. v1's
re-frame.registrar/register-handleris the implementation reference for handler-slot replacement. v1 also hasclear-handlersfor per-kind / single-id deregistration. What is new in re-frame2: the per-kind rules below are made normative (v1's behaviour is mostly the same, but unspecified); the run-to-completion guarantee that in-flight work survives reload is a contract; machine handlers (registered as ordinary:evententries viareg-machine, per 005 §Registration) are added to the closed-kinds discipline. Machine guards and actions are not registry kinds — they are machine-scoped, declared in eachcreate-machine-handlerspec's:guards/:actionsmaps (per the §Registry model callout above) — so hot-reload of a guard/action body happens implicitly when the enclosing machine's:eventregistration is replaced.
The hot-reload contract¶
Five guarantees apply uniformly across every registry kind:
- Re-registration is non-destructive to in-flight work. Whatever was already executing — an event currently in the drain loop, an
:fxwalk in progress, an:alwaysmicrostep midway through its loop — finishes against the resolved (pre-replacement) handler. The replacement applies to future lookups, not to handler invocations already begun. - Cached values invalidate on relevant re-registration. Re-registering a
:subdisposes that sub's cache slot in every frame; next subscribe rebuilds. Other kinds do not have caches, so no invalidation is needed. - Active machine instances continue with their captured spec. Each instance captures its machine spec — including the
:guards/:actionsmaps — at spawn time (per §How registrations interact with active machine instances, below). Re-registering the machine's:eventslot affects future spawns; active instances run to their natural lifecycle against the captured spec. To pick up new bodies in an active instance without re-spawning, declare guards/actions through Clojure vars and re-defthe var (the call site resolves the var every microstep — ordinary Clojure var-hot-reload, not a registry mechanism). - The trace bus emits
:rf.registry/handler-replacedon every re-registration (009-Instrumentation §Core fields) — devtools (10x, re-frame-pair) refresh their view from this event. - Dispatch is not paused. There is no "reload window" during which the runtime is unavailable. Re-registration is a registry-slot swap; the rest of the runtime continues operating.
Per-kind rules¶
The kinds are listed in the order they appear in §Registry model — the canonical kind keyword set.
| Kind | What re-registration does | In-flight behaviour | Cached state | Trace event |
|---|---|---|---|---|
:event (plain) |
Replace the handler fn for this event-id | Events currently in process-event! finish against the old fn (run-to-completion) |
None | :rf.registry/handler-replaced |
:event (machine handler — same :event kind, registered via reg-machine) |
Replace the machine's :event slot — i.e. the whole create-machine-handler body, including its captured :guards / :actions maps. Machine guards and actions are not registry kinds (per §Registry model); they replace only as part of the enclosing :event slot. |
Active instances are not affected — they continue running with the spec captured at spawn time. The next [:rf.machine/spawn ...] (or :invoke) creates instances against the new spec. Microsteps in-flight on existing instances finish against their captured guards/actions. |
None for the spec; active instance snapshots remain at [:rf/machines <id>] |
:rf.registry/handler-replaced (with :tags {:active-instances <count>} for visibility) |
:sub |
Replace the sub body and :<- chain. Dispose the cache slot for this query in every frame. |
Subscribers reading the old reaction get the old value once more; next deref recomputes through the new body | Cache slot is disposed; sub-cache reference counting carries new readers | :rf.registry/handler-replaced + :sub/disposed per cleared cache slot |
:fx |
Replace the fx handler fn | :fx walks already in do-fx finish against the old fn (rule 1); subsequent walks see the new fn |
None | :rf.registry/handler-replaced |
:cofx |
Replace the cofx handler fn | Cofx already injected into an interceptor chain are bound to the old fn for that event; subsequent events see the new fn | None | :rf.registry/handler-replaced |
:view |
Replace the view fn | Currently-rendering views finish against the old fn; the substrate's next render cycle picks up the new fn | None | :rf.registry/handler-replaced |
:frame |
Surgical update of the frame's metadata; live app-db, sub-cache, queue all preserved |
Per 002 §Re-registration — surgical update | None disposed | :rf.registry/handler-replaced (frame metadata semantics owned by 002) |
:app-schema |
Replace the schema attached at the path | In-flight validation finishes against the old schema; next event-handler completion validates against the new schema | None | :rf.registry/handler-replaced |
:route |
Replace the route handler fn / pattern | Currently-handling navigation finishes against the old route handler; next navigation resolves the new one | None | :rf.registry/handler-replaced |
:head |
Replace the head-model contributor | Currently-rendering SSR responses finish against the old fn (request-scoped frame); CSR re-renders pick up the new fn | None | :rf.registry/handler-replaced |
:error-projector |
Replace the error projector fn | Errors mid-projection finish against the old fn; subsequent errors resolve the new fn | None | :rf.registry/handler-replaced |
Re-registration of a different function — collision warning¶
A re-registration with a different function (not just an updated source-coords pair on the same fn) is silent last-write-wins. This can mask collisions when two namespaces accidentally use the same id (:save from feature A clobbering :save from feature B).
The runtime can be configured to warn at registration time when an id is reassigned to a different fn — recommend turning this on in dev. The detection compares fn identity (or, in CLJS reference, source-coord pairs from §Source-coordinate capture). A re-eval of the same source file produces the same (file, line) pair and is silent; a different file or line reassigning the id surfaces :rf.warning/registration-collision.
How registrations interact with active machine instances¶
Machine instances are a special case worth pinning, because v1 has no analogue. A machine is registered as an ordinary :event entry (per 005 §Registration) — there is no :machine, :machine-action, or :machine-guard registry kind (per §Registry model). Hot-reload semantics flow from that single registration:
- The machine spec (the whole
create-machine-handlerbody, including its:guards/:actionsmaps) is the:eventslot's payload. Re-registering replaces the slot atomically — exactly like any other event handler — and emits:rf.registry/handler-replaced(with:tags {:active-instances <count>}for visibility on the machine case). - Active instances are not re-spawned. Each active instance carries the spec it captured at spawn time (when the
[:rf.machine/spawn ...]fx fired). Re-registering the:eventslot affects future spawns; in-flight instances continue running against their captured spec. - Cross-machine reuse of a guard/action body is via ordinary Clojure vars — define the fn as a var; reference the var from each machine's
:guards/:actionsmap. Re-def-ing the var picks up at every call site through ordinary var resolution. There is no framework-managed global registry for guard/action bodies (per 005 §Registration — Globally-registered guards/actions vs machine-scoped (RESOLVED)).
The asymmetry is deliberate. Editing a machine's :on table and saving means new transitions apply to new spawns only; instances mid-flight continue along the path they were already following. Editing a shared var that a guard or action references means every call site (across every machine, across every active instance) picks up the new body on its next invocation — but that is ordinary Clojure-var hot-reload, not a registry mechanism.
Hot-reload trace surface¶
Every re-registration emits :rf.registry/handler-replaced with a stable shape:
{:operation :rf.registry/handler-replaced
:tags {:kind :event ;; or :sub, :fx, etc.
:id :user/login ;; the re-registered id
:source-coords {:file "..." :line 42}
:previous-coords {:file "..." :line 38} ;; if available
:reason :hot-reload ;; or :programmatic, :test
;; per-kind extras:
:active-instances 3 ;; for :machine
:disposed-slots 12}} ;; for :sub
Devtools subscribe to this event and refresh their view (handler list, source-coord map, machine inspector). The :reason field distinguishes a save-triggered reload from explicit programmatic re-registration (test setup, REPL exploration, runtime hot-swap from re-frame-pair).
Edge cases¶
- Re-registering a sub mid-cascade. If a re-registration arrives while a drain cycle is in flight (host-async event delivered between dequeues), the cache invalidation fires, but already-computed values for the in-flight event remain bound to that event's effect map. The next dequeue sees the new sub.
- Re-registering a frame's
:on-create. Per 002 §Re-registration — surgical update, the new:on-createis recorded but does not re-fire. Usereset-frameif you want the new init to run. - Re-registering a destroyed frame's keyword. Treated as a fresh
reg-frame; new frame container is created;:on-createfires. - Hot-reload in production builds. Production builds typically have no save-triggered reload. The path is still legal (REPL re-evaluation, plugin systems) but rare.
Per-kind index (non-normative)¶
A pointer-only summary of the registration functions and the per-Spec docs that own each kind's contract. The metadata-map shape and required/optional keys for the standard keys are defined above (§The metadata map); per-kind extensions are defined in the linked Specs.
| Kind | Function(s) | Owning Spec |
|---|---|---|
| Event handler | reg-event-db, reg-event-fx, reg-event-ctx |
002-Frames.md |
| Subscription | reg-sub |
006-ReactiveSubstrate.md |
| Effect | reg-fx |
002-Frames.md, 011-SSR.md (for :platforms) |
| Cofx | reg-cofx |
002-Frames.md |
| View | reg-view (defn-shape macro) / reg-view* (plain fn) |
004-Views.md |
| Frame | reg-frame |
002-Frames.md |
| App-db schema | reg-app-schema |
010-Schemas.md |
| Route | reg-route |
012-Routing.md |
| Head model | reg-head |
011-SSR.md |
| Error projector | reg-error-projector |
011-SSR.md |
Schema integration¶
Per 010-Schemas.md: in dynamic hosts, the :spec metadata key holds a Malli schema. The CLJS reference validates against :spec in dev builds at the appropriate boundary (event vector before handler runs; sub return after compute; app-db after each handler). In production the validation is elided.
In static hosts, the type system handles shape correctness instead of :spec. The metadata-map shape is the same; the :spec key may be omitted, or used as documentation for runtime inspection without runtime validation.
The exact validation timing rules and dev-vs-prod elision live in Spec 010.
:doc is dev-warned when absent¶
The :doc key SHOULD appear on every reg-* registration. The metadata-map shape itself does not structurally require it (the registration succeeds without :doc; the schema marks the key {:optional true} per Spec-Schemas §:rf/registration-metadata). What the runtime does require, in dev builds only, is visibility — every registration that omits :doc MUST emit :rf.warning/missing-doc exactly once per (kind, id) pair so the omission surfaces in tooling without silently accumulating undocumented handlers.
Normative obligations:
- Emission gate. The warning is emitted on every
reg-*call whose final metadata-map (after macro merge of source coords) carries no:dockey, or where:docisnilor an empty string. The emission goes through the trace surface defined in 009-Instrumentation §The trace event model and carries:op-type :warning. - Suppression. The warning fires at most once per
(kind, id)pair within a given runtime process. Re-registering the same id (hot-reload save→re-eval) does not re-fire the warning; a different id under the same kind does. The suppression cache lives alongside the existing one-shot warning caches (:rf.warning/plain-fn-under-non-default-frame-once,:rf.warning/boundary-without-spec); destruction-recreation of the frame resets it as the others do. - Production elision. Per 009 §Production builds: zero overhead, zero code, the dev-only trace surface is gated on
re-frame.interop/debug-enabled?(alias ofgoog.DEBUG). The closure compiler eliminates the gated branch in:advancedproduction builds. Production binaries carry no:rf.warning/missing-docmachinery. - Kind coverage. Every kind in the §Registry model table (
:event,:sub,:fx,:cofx,:view,:frame,:route,:app-schema,:head,:error-projector,:flow) is in scope. Programmatic re-registrations through internal helpers (re-frame.core/-reg-event-dband siblings) that bypass the public macro path are out of scope — the warning fires from the macro layer, where the registration metadata is first composed. - Trace envelope. The trace event carries
:operation :rf.warning/missing-doc,:tags {:kind <kind> :id <id> :source-coords <captured-coords>}. Per 009 §Where trace emission lives the emission site is the macro-expandedreg-*body inregistrar.cljc. The recovery classification is:ignored— the registration completes normally; the warning is a diagnostic surface, not a gate.
The dev nudge is deliberate: documented handlers are the difference between a registry an agent can navigate and a registry it cannot. Making the warning one-shot per (kind, id) keeps the dev stream readable while ensuring the omission is visible in 10x, re-frame-pair, and any other consumer of the trace bus.
Hot-reload interaction. The warning is suppressed across re-registrations of the same
(kind, id)pair — a save→re-eval that re-registers:cart/add-itemwith no:docdoes not re-emit the warning. The expected workflow is: warning fires once, the developer adds:doc, the warning never fires again for that id. Adding then later removing:docre-fires the warning on the next dev-process boot (the suppression cache is per-process, not persisted).
Open questions¶
Per-kind metadata schemas (RESOLVED rf2-kxs6j)¶
The metadata map is open, but each kind has a documented set of keys it cares about. Per Spec-Schemas §Per-kind refinements, the catalogue ships per-kind narrowed schemas — :rf/event-handler-meta, :rf/sub-meta, :rf/fx-meta, :rf/cofx-meta, :rf/view-meta, :rf/machine-meta, :rf/flow-meta, :rf/app-schema-meta, :rf/head-meta, :rf/error-projector-meta, and the route-shaped :rf/route-metadata — each :merge-composed with the base :rf/registration-metadata open shape. AI scaffolders and conformance harnesses validate per-kind metadata at registration time against the corresponding refinement.
Resolved decisions¶
A pointer-only index of decisions taken in this Spec. Each entry's load-bearing prose lives in the linked section above (or in the linked sibling Spec).
| Decision | Pointer |
|---|---|
:doc is SHOULD (dev-warned) — absent registrations emit :rf.warning/missing-doc once per (kind, id) pair in dev; production elides the check; the metadata schema keeps :doc {:optional true} (the warning is the nudge, not a structural gate) |
§:doc is dev-warned when absent, 009 §Where trace emission lives, Spec-Schemas §:rf/registration-metadata |
:interceptors inside the metadata-map of reg-event-* is not a valid position — the chain MUST be the positional vector slot; mis-placement silently drops the chain and emits :rf.warning/interceptors-in-metadata-map |
§Allowed forms of the middle slot, Conventions §:interceptors is positional, not metadata |
Every reg-* returns its primary id — the keyword (or path, for reg-app-schema) the caller registered with |
§Return value, Conventions §reg-* return-value convention |
| Re-registration is non-destructive to in-flight work; cached values invalidate on relevant re-registration; active machine instances continue with their captured spec; dispatch is not paused | §The hot-reload contract |
Re-registration with a different fn is silent last-write-wins by default; the runtime can warn at registration time via :rf.warning/registration-collision (recommended on in dev) |
§Re-registration of a different function — collision warning |
Machine guards and actions are NOT registry kinds — they are machine-local declarations inside each create-machine-handler spec's :guards / :actions maps; hot-reload flows through the enclosing machine's :event slot |
§Canonical ownership boundaries, §Registry model — the canonical kind keyword set |
Cross-references¶
- 002-Frames §The public registrar query API — the runtime side of the query API.
- 010-Schemas —
:specmetadata key and validation timing. - 009-Instrumentation §Error contract — error events emitted by registration validation failures.
- Construction-Prompts §CP-1 — how AIs scaffold registered events.
- Spec-Schemas §:rf/registration-metadata — the canonical shape for metadata.