Surfaced while making the /v1 models gateway (#631) a clean plugin, from @kriszyp's review on #1616.
Problem
A component that registers REST-served resources has no supported way to ensure REST is actually serving. defaultConfig.yaml ships no rest section, and REST's middleware chain only activates when some config declares rest/REST. So on a bare instance (no apps), a core/built-in component can register resources that are silently unservable.
#1616 worked around this by reaching into core twice — a componentLoader branch naming the feature by hand, plus a new exported REST.ensureStarted(). Both have been removed (the gateway now documents that it requires a rest section), but the underlying gap remains and the next component to hit it will be tempted by the same hack.
Why the naive fix is wrong
Forcing REST to start early is not just a layering smell — it's incorrect. The activation ran after root plugins but before loadComponentDirectories(), which is where application configs load. REST.handleApplication sets httpOptions then hits if (started) return, so once something has force-started REST, a later app's own rest options are silently discarded:
rest: { webSocket: false } ignored (ws already registered)
urlPath / host restrictions not applied to the shared dispatcher
- middleware ordering via
before: 'rest' / after: 'rest' cannot be expressed against that listener
So any mechanism here must defer activation until the effective REST options are known — i.e. after application directories load — while still retaining a stable rest middleware name.
Ask
A first-class way for a component to declare "I serve REST resources; ensure REST is active", resolved after app load with the effective options. Shape is open — a scope-level declaration, a dependency/capability the loader resolves, or an explicit ordering hook.
Related: whether such a component's resources belong in the shared resources registry at all, or should be provided as scoped/custom resources (@kriszyp raised this on #1856).
Context: #631 (Phase 4 of #510), #1616.
🤖 Filed with Claude Code on behalf of @heskew
Surfaced while making the
/v1models gateway (#631) a clean plugin, from @kriszyp's review on #1616.Problem
A component that registers REST-served resources has no supported way to ensure REST is actually serving.
defaultConfig.yamlships norestsection, and REST's middleware chain only activates when some config declaresrest/REST. So on a bare instance (no apps), a core/built-in component can register resources that are silently unservable.#1616 worked around this by reaching into core twice — a
componentLoaderbranch naming the feature by hand, plus a new exportedREST.ensureStarted(). Both have been removed (the gateway now documents that it requires arestsection), but the underlying gap remains and the next component to hit it will be tempted by the same hack.Why the naive fix is wrong
Forcing REST to start early is not just a layering smell — it's incorrect. The activation ran after root plugins but before
loadComponentDirectories(), which is where application configs load.REST.handleApplicationsetshttpOptionsthen hitsif (started) return, so once something has force-started REST, a later app's ownrestoptions are silently discarded:rest: { webSocket: false }ignored (ws already registered)urlPath/hostrestrictions not applied to the shared dispatcherbefore: 'rest'/after: 'rest'cannot be expressed against that listenerSo any mechanism here must defer activation until the effective REST options are known — i.e. after application directories load — while still retaining a stable
restmiddleware name.Ask
A first-class way for a component to declare "I serve REST resources; ensure REST is active", resolved after app load with the effective options. Shape is open — a scope-level declaration, a dependency/capability the loader resolves, or an explicit ordering hook.
Related: whether such a component's resources belong in the shared
resourcesregistry at all, or should be provided as scoped/custom resources (@kriszyp raised this on #1856).Context: #631 (Phase 4 of #510), #1616.
🤖 Filed with Claude Code on behalf of @heskew