Appearance
Planned: Database-per-Tenant Migration
A planned architectural change to essence's multi-tenancy model. This is a feature initiative, not part of the public-launch roadmap — 0.12.0 shipped without touching tenancy at all. It has its own branch, timeline, and version-bump wave, and is picked up independently of the launch work.
Decision (2026-08-25): adopt real database-per-tenant
The original open question was whether stancl/tenancy was earning its weight as a hard dependency, given essence only used row-scoping (BelongsToTenant + bot_id) and every other Stancl feature — DB switching, domain/subdomain resolution, tenant lifecycle jobs/events — was either explicitly disabled or configured but never wired up (config/tenancy.php's bootstrappers array and TenancyServiceProvider::bootEvents() are both entirely commented out).
Decision: go the other way — adopt real database-per-tenant multi-tenancy, the thing Stancl was actually built for, instead of dropping it for a lighter row-scoping shim. Motivation: row scoping means every bot-owned table across every companion package carries a bot_id column and a global scope; database-per-tenant removes that column entirely and gets real data isolation between bots as a side effect. This is a bigger commitment to Stancl, not a smaller one.
Should stancl/tenancy stay a hard dependency? Yes, more than before.
Scope, audited 2026-08-25
BelongsToTenant is imported directly by 14 model files across 7 companions (affiliates, announcements, billing, settings, user-management, user-wallet, plus essence's own BotUser) — every one of them needs to drop the trait and its bot_id column once their tables move to per-tenant databases. Everything else Stancl-related in essence is either unused (TenantScope imported but never referenced in tbe-user-wallet/BotUserWallet.php) or thin/doc-only (TenantCouldNotBeIdentifiedById only appears in @throws docblocks, never caught).
Spans essence and all 7 companion packages that have models, plus a real production data migration (vshop currently has 6 live bots on the shared-DB-with-bot_id model).
Central vs. per-tenant split, decided
telegram_users stays central — one person can talk to multiple bots, so it's one row shared across all of them, not bot-scoped. bots itself also stays central, by construction: Stancl's tenant registry (the thing that resolves which database to switch to) has to live somewhere every request can reach before tenancy initializes. Everything else — bot_users and every table currently BelongsToTenant across every companion — moves into each bot's own database. The bot_users.telegram_user_peer_id → telegram_users foreign key becomes a cross-database reference with no DB-level constraint once bot_users moves to a tenant database; integrity has to be enforced at the application layer instead.
Cutover
A maintenance-window migration, not zero-downtime. Telegram retries webhook deliveries on non-2xx responses, so a few minutes down (per bot, or all 6 at once) loses nothing.
Work items
- [ ] Design the per-tenant database naming/provisioning convention (Stancl's default:
prefix + tenant_id + suffix) and confirm a single MySQL server hosting all tenant databases is sufficient at this scale - [ ] Wire up the tenant-creation pipeline that's currently scaffolded but dark:
TenancyServiceProvider::events()'sTenantCreated→CreateDatabase/MigrateDatabasejobs are commented out, andbootEvents()never actually callsEvent::listen()at all - [ ] Enable
DatabaseTenancyBootstrapperinconfig/tenancy.php(currently commented out, "dark since introduction" per that file's own comment) - [ ] Split essence's own migrations:
bots/telegram_usersstay in the central migration path,bot_users/state_data/inline_confirmations/message_metasmove to a tenant migration path - [ ] Same split for every companion package with models — in practice this is "move everything," since nothing else needs to stay central
- [ ] Write and rehearse the production data migration against a clone before touching the real thing: create each bot's database, copy that bot's scoped rows out of the shared tables, verify row counts/spot-check data, only then cut the app over
- [ ] Version-bump wave: every affected companion's own
essenceconstraint moves again — a second breaking-change wave after the 0.12.0 locale-lazy one, so give it its own version bump per repo rather than folding it into an unrelated release - [ ] Document the migration path for vshop, the one existing consumer