Finding
To learn what a plugin changes about a running host, you read its StartAsync and follow every context.Export(...) by hand. registry.json deliberately does not carry this — it is governance metadata, and ADR-009 put wiring in code on purpose. That decision is right and this issue does not reopen it.
But the consequence is avoidable. The runtime already knows the answer: IExtensionPointRegistryStore, IPluginExtensionRegistrationStore, PluginExtensionRegistrationSnapshot, SurfaceApiRouteInventory. Nothing presents it.
What Frappe gets for free, and what it pays
hooks.py is one file per app that states every host modification — doc_events, scheduler_events, override_whitelisted_methods, permission_query_conditions, before_request, extend_bootinfo, and thirty more. One file, one answer.
The price is that the file is a convention, not a boundary: Frappe's real extension surface is "anything importable", monkey-patching included, and nothing proves hooks.py is complete. Our surface is provably complete (ExtensionPointCatalogCompletenessTests) and enforced at build time by CAL0001–0004. We should not trade that away for a declaration file.
We can have the discoverability without the trade, because we already hold the data.
Proposal
Generate the view from the registry snapshot instead of asking the author to write it:
callora plugin inspect <path|pluginId> — static, no running host: read the assembly and registry.json and report the entry type, declared capabilities and requirements, dependencies, contractVersion, and the extension points the plugin implements. This is the manifest an operator wants before installing.
- A per-plugin Admin screen — live, from the registry stores: which extension points the plugin actually registered at, which routes it attached (
SurfaceApiRouteInventory), which business events it subscribes to, which services it decorates or replaces, and — pointedly — where it collides with another plugin.
(1) and (2) answer different questions and both are worth having: one is "what will this do to my host", the other is "what is it doing to my host right now".
Why this one first
Of the findings from the Frappe review, this is the cheapest: the data is already collected and already tested. It is presentation, not new machinery. It is also a prerequisite for the conflict reporting in #346 — the backend needs somewhere to show a conflict once it can detect one.
Done when
Found while reviewing frappe/frappe against this repository.
Finding
To learn what a plugin changes about a running host, you read its
StartAsyncand follow everycontext.Export(...)by hand.registry.jsondeliberately does not carry this — it is governance metadata, and ADR-009 put wiring in code on purpose. That decision is right and this issue does not reopen it.But the consequence is avoidable. The runtime already knows the answer:
IExtensionPointRegistryStore,IPluginExtensionRegistrationStore,PluginExtensionRegistrationSnapshot,SurfaceApiRouteInventory. Nothing presents it.What Frappe gets for free, and what it pays
hooks.pyis one file per app that states every host modification —doc_events,scheduler_events,override_whitelisted_methods,permission_query_conditions,before_request,extend_bootinfo, and thirty more. One file, one answer.The price is that the file is a convention, not a boundary: Frappe's real extension surface is "anything importable", monkey-patching included, and nothing proves
hooks.pyis complete. Our surface is provably complete (ExtensionPointCatalogCompletenessTests) and enforced at build time by CAL0001–0004. We should not trade that away for a declaration file.We can have the discoverability without the trade, because we already hold the data.
Proposal
Generate the view from the registry snapshot instead of asking the author to write it:
callora plugin inspect <path|pluginId>— static, no running host: read the assembly andregistry.jsonand report the entry type, declared capabilities and requirements, dependencies,contractVersion, and the extension points the plugin implements. This is the manifest an operator wants before installing.SurfaceApiRouteInventory), which business events it subscribes to, which services it decorates or replaces, and — pointedly — where it collides with another plugin.(1) and (2) answer different questions and both are worth having: one is "what will this do to my host", the other is "what is it doing to my host right now".
Why this one first
Of the findings from the Frappe review, this is the cheapest: the data is already collected and already tested. It is presentation, not new machinery. It is also a prerequisite for the conflict reporting in #346 — the backend needs somewhere to show a conflict once it can detect one.
Done when
callora plugin inspectworks against an uninstalled plugin directory, without a host or a databasetests/TestPlugins/ExportingPlugindocs-site/reference/cli.mddocuments the verbFound while reviewing frappe/frappe against this repository.