Middleware FAQ¶
What are middleware types?¶
Middleware is hosted extension behavior owned by explicit contracts. The main execution
types are in-process Rust, pure JSON-e, JSON-e Flow, command/stdio, unmanaged local HTTP
JSON, channel_json supervision, Sensorium connector middleware, and middleware-hosted
Inquirium runtime adapters. The old supervised http_local_json executor is retired:
its config and package forms are rejected, and no runtime implementation remains.
Distribution is a separate axis: a middleware can be factory-bundled,
profile-distributed, or operator-installed regardless of execution type.
For the detailed type descriptions, registration shapes, and examples, see Middleware HOWTO. For the boundary between the pure evaluator and host-owned flow steps, see the JSON-e and JSON-e Flows FAQ.
When should a module use channel_json?¶
Use channel_json for an eligible supervised module whose loopback listener exists
only so the Node host can attach, invoke, observe, or expose a host-mediated operator
surface. The module initiates one authenticated session to the daemon's shared
listener; it does not receive durable authority or a replay queue from that session.
Keep an intentional product, peer, browser, or provider listener when it is part of
the component contract. Mixed modules migrate only their host-control plane and keep
the product listener explicit. Never register the same semantic route through both
transports as an implicit fallback. New supervised modules and packages must use
channel_json; a retained product HTTP listener is configured as a separate domain
surface, not as the middleware executor.
Bundled modules make current ownership visible through factory_executor and
product_listener_retained. All 18 bundled modules use channel_json; a host-only
module has no factory port. Eight modules retain separately owned product listeners.
Daemon configurations, persisted settings, loose config artifacts, and package
manifests that name http_local_json are rejected before effects with an explicit
migration diagnostic. Node never silently converts them. Stale listener keys in a
channel-only bundled module subtree are likewise rejected rather than ignored.
Python modules should reuse the standard channel adapter instead of implementing WebSocket framing. See Authoring a channel module.
For Inquirium, the model-runtime catalog may select channel_json with a module id,
declared invoke path, and timeout. This changes transport only: runtime/ref, model
binding, policy, and response validation remain host-owned.
What happens when required middleware disappears?¶
The Node does not keep routing calls to a consumer whose required provider is gone.
It resolves exact capability-and-contract-digest dependencies, marks affected
consumers non-routable, drains and stops them in dependent-first order, and reports
dependency_unavailable. A dedicated reconciliation loop resumes consumers
provider-first only when the exact requirements are observed ready again; reading
health/status never starts or stops a component. A same-named but
contract-mismatched or incorrectly pinned provider is not a recovery.
For a transient channel loss, the supervisor injects the bounded reconnect grace into the child. Current generated profiles use 1 second; a standalone Python runtime with the variable absent retries for 0 seconds. Grace exhaustion or a failed application heartbeat terminates and cleans the child before the configured restart policy runs. No in-flight request is replayed transparently.
This lifecycle transition cleans up typed host-local resources. Durable, external, and federated effects retain their own transaction, journal, compensation, supersession, or approval semantics. Process shutdown is never presented as undo of such an effect. See Declaring component dependencies and effect recovery.
What is Role Middleware?¶
Role Middleware is not an execution type. It is a specialization pattern: a middleware component receives a role-shaped request and dispatches it to behavior selected by role, capability, or service identity. It can be implemented as supervised channel JSON, JSON-e Flow, or another registered middleware form.
For concrete supervised channel JSON and JSON-e Flow examples, see Role Middleware in the Middleware HOWTO.
Where can middleware attach to the node data path?¶
The current peer-message chains are pre-input, inbound-peer, pre-send, and
post-chain observers. Other middleware surfaces include claimed local routes,
role/service dispatch, host capability bridges, broadcast handling, operator UI
surfaces, and read-only observer/audit hooks. The important rule is that each attachment
has its own request contract and allowed decisions; there is no single universal
interceptor contract.
For the complete hook map, decisions, examples, and compatibility notes, see Middleware hook HOWTO.
How does one supervised middleware distinguish calls from multiple hooks?¶
A supervised channel middleware may reuse one declared invoke path for multiple
registrations, but the path is not the semantic discriminator. The middleware should
inspect the request envelope, especially chain_kind, envelope_kind, and the
schema-specific payload shape. Separate paths are often clearer operationally, but
even then the envelope remains the source of truth.
For request examples and branching sketches, see multiple-hook dispatch in the Middleware HOWTO.
Where are distribution and packaging rules described?¶
Execution type and distribution model are separate. The same middleware behavior can be compiled into the node, shipped as a profile definition, or installed as an operator package. Distribution changes trust posture and lifecycle, not the runtime contract by itself.
For the distribution model reference, see Distribution models in the Middleware HOWTO.