Reference
Migration Constraints
Extension migrations are intentionally narrow. They exist so an extension can own its own storage without weakening core schema boundaries.
Boundary Rule
Every extension-owned table must start with:
{vendor}_
For vendor my_vendor, valid table names include:
my_vendor_notes
my_vendor_settings
my_vendor_schema_migrations
Invalid table names include:
pages
users
settings
other_vendor_notes
core_table_violation
The boundary is a safety rule, not a naming preference.
Runner Contract
MyronExtensionMigrationRunner::run() accepts:
run(string $vendor, PDO $pdo, array $migrations): void
The vendor must be snake_case:
^[a-z][a-z0-9_]*$
The runner creates this tracking table automatically:
{vendor}_schema_migrations
It then skips already-applied versions and runs each new version in a transaction.
Checked SQL Statements
Array SQL migrations are checked when they begin with one of these statement shapes:
| Statement | Boundary checked |
|---|---|
CREATE TABLE ... | Created table must start with {vendor}_. |
ALTER TABLE ... | Altered table must start with {vendor}_. |
DROP TABLE ... | Dropped table must start with {vendor}_. |
CREATE INDEX ... ON ... | Indexed table must start with {vendor}_. |
CREATE UNIQUE INDEX ... ON ... | Indexed table must start with {vendor}_. |
If the table does not start with the vendor prefix, the runner throws:
Extension migrations may only modify tables prefixed with {vendor}_.
Callable Migrations
Migration files may return callables. Callable migrations are useful for small data transforms or database-specific operations, but the static SQL prefix check cannot inspect arbitrary PHP.
The contract still applies:
- callable migrations must only touch
{vendor}_*tables, - callable migrations must not mutate core or cross-vendor tables,
- callable migrations must not store secrets,
- callable migrations must not bypass tracking.
Prefer array SQL for schema creation when possible because the runner can inspect it.
Versioning
Use stable, descriptive version keys:
return [
'v001_create_notes_table' => [
'CREATE TABLE IF NOT EXISTS my_vendor_notes (...)',
],
'v002_add_note_status' => [
"ALTER TABLE my_vendor_notes ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT 'draft'",
],
];
Once a version has shipped, do not change its meaning. Add a new version for subsequent changes.
Human Path
The developer authors migration files under:
app/extensions/{vendor}/migrations/
The operator registers and enables the extension. On boot, the loader runs unapplied migrations before registering runtime contributions.
AI Path
An AI coding agent can author local migration files, check table prefixes, and run verification. An installed MCP client cannot run migrations directly and must not be documented as having migration-control parity.
Scopes
Migrations are not called through API or MCP scopes. They run as part of extension loading. The extension grant still determines whether the extension is loadable.
Artifacts
app/extensions/{vendor}/migrations/*.php{vendor}_schema_migrations- extension-owned
{vendor}_*tables
Safety Boundary
Do not document these operations as allowed:
- creating or altering core tables,
- creating or altering another extension's tables,
- storing API keys or secrets in migration files,
- editing
{vendor}_schema_migrationsby hand, - running migration PHP files outside the loader to get around a load error.
Verification
Run:
php app/tests/verify-extension-system-foundation.php
The test confirms both the positive and negative paths:
| Check | Expected result |
|---|---|
Reference migration creates myron_sample_pings | Pass. |
Runner creates myron_sample_schema_migrations | Pass. |
| Non-prefixed table migration is attempted | Rejected. |
Failure Modes
| Failure | Source | Safe response |
|---|---|---|
Extension migration vendor must be snake_case. | Vendor validation. | Use a valid manifest vendor. |
Extension migrations may only modify tables prefixed with ... | SQL table isolation check. | Rename the table under {vendor}_*. |
| Migration version never records | Migration threw before tracking insert. | Fix the migration and rerun through normal loader boot. |
| Migration partially applies | Database-specific transaction behavior or DDL semantics. | Keep migrations idempotent and review database behavior before shipping. |
| Callable migration mutates core state | Contract violation. | Move state into extension-owned tables or return to Evaluate for a new primitive. |