Core Concepts
Load Lifecycle
The loader turns local extension files into runtime contributions. It does not install packages from a marketplace, fetch dependencies, or sandbox third-party code. It scans local files, validates their contracts, checks grants, and registers only what passes.
Human Path
A developer authors files under app/extensions/{vendor}/. An operator registers and enables the extension. On boot, MyronCMS loads valid contributions.
AI Path
An AI coding agent can author local files and run verification. A running MCP client cannot create files or force the loader to trust an extension. It can only discover and call loaded, exposed, scope-allowed runtime tools.
Loader Sequence
MyronExtensionLoader::boot() follows this sequence:
- Reset prior in-memory manifests, load failures, and admin routes.
- Scan
app/extensions/*/manifest.json. - Normalize the directory vendor by replacing hyphens with underscores.
- Parse
manifest.jsonwith the expected vendor. - Store the parsed manifest in memory.
- Check grant state when extension grant tables exist.
- Skip unregistered extensions.
- Skip disabled extensions.
- Reject extensions whose manifest requires ungranted scopes.
- Run extension migrations when
hasMigrationsis true. - Require
bootstrap.phpif it exists. - Register returned actions through
ActionRegistry::registerExtension(). - Register returned MIC resolvers through
MicElementRegistry::registerExtension(). - Register returned mail transports through
MailTransportRegistry::registerExtension(). - Mount declared admin routes whose handler files exist.
- Clear prior load errors for successfully loaded vendors.
- Record manifest, migration, or bootstrap failures in
extension_load_errors.
Core requests continue when an extension fails to load. The failure is logged and exposed through the Installed Extensions surface.
Bootstrap Shapes
bootstrap.php may return one direct action:
return new MyVendorHelloAction($sdk);
It may return a list of actions:
return [
new MyVendorHelloAction($sdk),
];
It may return named contribution arrays:
return [
'actions' => [new MyVendorHelloAction($sdk)],
'micElementTypes' => [
'catalog' => new MyCatalogMicResolver($sdk),
],
'mailTransports' => [
'resend' => new ResendMailTransport($sdk),
],
];
The loader ignores entries that do not implement the expected interface for the contribution slot.
Migrations
When hasMigrations is true, migration files are loaded from:
app/extensions/{vendor}/migrations/*.php
Migration files may return versioned SQL lists or callables. SQL statements may create, alter, drop, or index only tables prefixed with {vendor}_. Applied versions are tracked in {vendor}_schema_migrations.
Runtime Contributions
| Contribution | Registration point | Runtime effect |
|---|---|---|
MyronAction | ActionRegistry::registerExtension() | Adds namespaced /api/v1/x/{vendor}/... actions and optional MCP tools. |
| MIC resolver | MicElementRegistry::registerExtension() | Adds x.{vendor}.{slug} rich-text insertion resolution. |
| Mail transport | MailTransportRegistry::registerExtension() | Adds x.{vendor}.{slug} outbound mail transport candidates. |
| Admin route | ExtensionLoader::getAdminRoutes() | Mounts isolated admin route handlers under /x/{vendor}/.... |
Failure Modes
| Failure | Result |
|---|---|
| No extensions directory | Loader returns without registering extensions. |
| Manifest missing or invalid | Failure is recorded for that vendor. |
| Extension not registered | Loader skips the extension. |
| Extension disabled | Loader skips the extension. |
| Ungranted required scope | Loader records a failure. |
| Migration touches a core table | Migration runner rejects and records failure. |
| Bootstrap throws | Loader records failure. |
| Action has wrong URI/domain/schema | Action registry rejects and loader records failure. |
| Admin route handler missing | Route is not mounted. |
Verification
Run:
php app/tests/verify-extension-system-foundation.php
For a loaded action, verify all three layers:
- CLI foundation test passes.
- Direct API call to
/api/v1/x/{vendor}/...succeeds for a scoped caller. - MCP
tools/listshows the tool only whenexposeToMcp: trueand the caller holds the required scope.