Upgrading a scaffolded host
A host generated by smpy new or smpy create-host owns its Alembic history. Its initial revision reflects the models installed at generation time; it does not import the framework repository's historical revisions. Updating packages and running schema autogenerate cannot reproduce historical data transformations.
Check and prepare
Back up the database and review the release notes before upgrading packages. From the project root, with the host's Python environment active:
uv run smpy doctor
uv run alembic -c host/alembic.ini heads
uv run smpy migrations prepare mail.settings_namespace_v1 --head <your-host-head>
uv run smpy migrations prepare users.hashed_bearer_tokens_v1 --head users@headReplace <your-host-head> with an actual head in your graph. Use users@head only if your graph already declares that label; otherwise pass the relevant host head id. Preparing the mail revision may advance the same branch, so inspect heads again before preparing the next revision. For flat hosts use alembic.ini instead of host/alembic.ini. The preparation command detects both layouts, or accepts --config path/to/alembic.ini.
Preparation writes one self-contained revision into that host's migrations/versions. It never connects to the database or applies the revision. The frozen recipes ship in the CLI package, and generated revisions import only Alembic and SQLAlchemy: future module/package upgrades cannot change their code. The first prepared recipe for an unlabelled module declares mail or users; existing labels are reused without redeclaration. --head is required and must resolve to one current host head. No upstream roots are grafted onto your graph.
Review and apply
Review and keep the generated revision in your application's version control. Do not generate a token invalidation revision for a baseline that is already compatible: applying it deliberately signs clients out.
| Upgrade id | Effect |
|---|---|
mail.settings_namespace_v1 | Move system-scope users.mailer to mail.backend and the six users.smtp_* transport keys to mail.*. Existing target values win; stale source rows are removed. Tenant/user settings are untouched. Downgrade reverses the mapping with the same target-wins rule. |
users.hashed_bearer_tokens_v1 | Delete access and refresh credentials before changing both token columns to String(64). Downgrade deletes credentials again and restores the legacy column types; it cannot recover secrets. |
Stop every old application worker, including background workers, before applying the token upgrade. Mixed old/new workers must not mint or use plaintext tokens during this upgrade. Clients using bearer tokens or sm_auth must sign in again. Other session cookies are unaffected. After reviewing and scheduling the upgrade:
uv run alembic -c host/alembic.ini upgrade heads
uv run smpy doctorRestart the application with the upgraded packages. Role provisioning remains an idempotent startup step; it needs no prepared data revision.
Compatibility checks
Modules expose read-only check_data_compatibility(db) hooks with semantic upgrade ids. Boot runs them before module settings hydration and module startup. Doctor (make doctor or smpy doctor) reports them as SM033 and exits nonzero on errors.
Unmoved system mail keys warn. Incompatible token columns or legacy token representations refuse boot outside development; development logs warnings. Checks inspect state, so equivalent manual ports are accepted without requiring upstream revision ids. A current schema-only baseline with empty token tables passes and does not trigger credential deletion.
A never-migrated database skips the initial check so the first-run wizard stays reachable. Checks replay after wizard/out-of-band migrations before deferred settings hydration/startup; an incompatibility keeps the setup gate closed. This is detection and explicit preparation for the two bundled upgrades, not an automatic migration system for arbitrary third-party module data.