Upgrade Guide
How to ship application releases to existing production installations without reseeding.
Standard upgrade
php artisan down --retry=60 # optional
git pull
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize
php artisan queue:restart
php artisan reverb:restart # if Reverb is running
php artisan upThat is the complete path for catalog modules and tenant permission vocabulary changes that follow the platform pattern.
User security settings (2FA, passkeys, sessions)
Deploy Backend + Frontend + Docs together. Run migrations before serving the SPA build that includes Profile → Security and the login TOTP step.
| Migration | Purpose |
|---|---|
2026_09_01_230027_add_session_metadata_to_personal_access_tokens_table | Session list metadata on Sanctum tokens |
2026_09_01_231207_create_passkeys_table | WebAuthn passkeys |
2026_09_01_231209_add_two_factor_columns_to_users_table | TOTP columns (tenant users) |
2026_09_01_231400_add_two_factor_columns_to_central_users_table | TOTP columns (central users) |
Set PASSKEYS_RELYING_PARTY_ID and PASSKEYS_ALLOWED_ORIGINS before enabling passkeys in production. See User security settings production readiness.
What migrate does for modules & RBAC
| Change type | Mechanism | Safe for existing data? |
|---|---|---|
| New default-included module | Data migration + DefaultModuleRegistrar | Yes — insert-only catalog; install only if workspace never had a subscription row |
| New permissions | Data migration + TenantPermissionSynchronizer | Yes — additive grants only; never resets customized roles |
| Schema | Normal Laravel migrations | Follow usual migration discipline |
Do not run in production upgrades
php artisan db:seedCatalogSeeder/ Central catalog seeders to “pick up” new modules- Role/permission seeders to “sync” RBAC
- Manual SQL that reactivates cancelled module subscriptions
- Any process that expects login to repair missing permissions
Tenant API & Webhooks (Settings → Developers)
Migrate-first platform capability (not a Marketplace module).
- Deploy Backend + Frontend + Docs together.
- Run
php artisan migrate --force— addspersonal_access_tokens.token_type,tenant_webhook_endpoints,tenant_webhook_deliveries, and grantssettings.manage_developersto owner/admin defaults. - Ensure a queue worker processes the
webhooksqueue (DeliverTenantWebhookJob). Includewebhooksin the Forge worker list (see Laravel Forge). - Scheduler runs
webhooks:prune-deliveries --days=90weekly (delivery payload retention). - Smoke: Settings → Developers → create API token → Bearer call → create webhook → Send test.
See Tenant API & Webhooks production readiness.
Short Links 1.1.0 → 1.2.0
Deploy Backend + Frontend + Docs together.
php artisan migrate --force— widensshort_links.codeto 64 chars; bumps catalogshort-linksto 1.2.0 (2026_08_23_231224_widen_short_link_codes_and_bump_version).- Set production env:
SHORT_LINK_BASE_URL(e.g.https://go.elosync.com),SHORT_LINK_MARKETING_URL,SHORT_LINK_BETA_URL. - Point short-domain DNS at the same Laravel app that serves
GET /r/{identifier}. - Confirm queue workers process
RecordShortLinkClickJob(default queue). - Deploy SPA after migrate — vanity slug create field and copy icons require the new frontend build.
- Smoke: create vanity slug → public redirect; pause/delete → branded 404;
npm run test:e2e:short-links.
Go-live: Short Links production readiness · ops Short Links deployment.
Platform polish lanes A, B, C
Deploy Backend + Frontend + Docs together. Frontend PR #143 is already on main; Backend PR #148 and Docs PR #170 must merge before production.
php artisan migrate --force— attachment tables,document_links, catalog version bumps (2026_08_23_*).- Deploy Backend before or with the SPA (receipt upload, document links, credit note / PO PDF+email APIs).
- Confirm
emailsqueue workers and Storage upload disk (Storage production readiness). - Smoke: documents, expenses, help-desk, tenant-dashboard, AI module Playwright scripts (
--workers=1).
Go-live audit: Platform polish A/B/C production readiness — No-Go until Backend/Docs PRs merge.
AI Assistant platform 1.0.0 → 1.0.1
After migrate (2026_08_21_010000–010300):
- Confirm catalog rows
ai(billable 1.0.1),ai-credits-1k,ai-credits-5k,ai-credits-20kare published. - Confirm permissions
ai.use,ai.manage,ai.confirmexist and default admin/manager maps include them. - Confirm scheduler runs
ai:rollover-monthly-credits(daily, overlapping locked, one server). - Set Central Settings → AI (
ai_enabled, provider, encryptedai_api_key, models, monthly included credits). - Deploy the SPA after migrate — Ask EloSync / Settings AI (
ai.manage) / Lead Copilot require the module entitlement. - Smoke: entitle
ai→ wallet grant → Ask EloSync → Lead Copilot; platform mode returns 402 when credits are exhausted or below the pre-provider ceiling.
1.0.1 hardens credit integrity (wallet lockForUpdate, pre-provider credit ceiling, request-path ensurePeriod), enforces ai.manage on Settings AI, and adds throttle:ai on message/copilot routes.
Go-live: AI production readiness · ops notes AI deployment.
AI workspace search 1.2.0 → 1.3.0
After migrate (2026_08_31_200000_bump_ai_module_version_to_1_3_0):
- Confirm catalog
ai.versionis 1.3.0 (migrate-only; do notdb:seed). - Deploy SPA + Mobile after Backend — Search workspace starter + Wave A+B+C citations.
- Smoke: Ask EloSync → Search workspace starter; confirm hits respect module entitlement and
*.view; citations stay same-app.
Go-live: AI workspace search production readiness · ops AI deployment.
AI Help Desk triage tools 1.3.0 → 1.4.0
After migrate (2026_09_15_040000_bump_ai_module_version_to_1_4_0):
- Confirm catalog
ai.versionis 1.4.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+help-desk→ Ask EloSync open tickets → propose status/assign/note → Confirm; deny resolve withouthelp-desk.close.
Go-live: AI Help Desk triage production readiness · ops AI deployment.
AI Task triage tools 1.4.0 → 1.5.0
After migrate (2026_09_15_050000_bump_ai_module_version_to_1_5_0):
- Confirm catalog
ai.versionis 1.5.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai(Tasks is default-included) → Ask EloSync overdue/due-today tasks → propose status/assign/note → Confirm; deny complete withouttasks.complete.
Go-live: AI Task triage production readiness · ops AI deployment.
AI Opportunity triage tools 1.5.0 → 1.6.0
After migrate (2026_09_15_060000_bump_ai_module_version_to_1_6_0):
- Confirm catalog
ai.versionis 1.6.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+opportunities→ Ask EloSync list stages / fetch opportunity → propose stage/assign/note → Confirm; deny stage withoutopportunities.update.
Go-live: AI Opportunity triage production readiness · ops AI deployment.
AI Invoice triage tools 1.6.0 → 1.7.0
After migrate (2026_09_15_070000_bump_ai_module_version_to_1_7_0):
- Confirm catalog
ai.versionis 1.7.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+invoices→ Ask EloSync fetch invoice / overdue → propose status/assign/note → Confirm; Draft→Unpaid needsinvoices.send; cancel needsinvoices.void.
Go-live: AI Invoice triage production readiness · ops AI deployment.
AI Expense triage tools 1.7.0 → 1.8.0
After migrate (2026_09_15_080000_bump_ai_module_version_to_1_8_0):
- Confirm catalog
ai.versionis 1.8.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+expenses→ Ask EloSync fetch expense / pending approval → propose status/assign/note → Confirm; approve needsexpenses.approve; reject needsexpenses.reject; paid via status needsexpenses.pay(andpaid_from_account_idon the expense when Accounting is entitled).
Go-live: AI Expense triage production readiness · ops AI deployment.
AI Project triage tools 1.8.0 → 1.9.0
After migrate (2026_09_15_090000_bump_ai_module_version_to_1_9_0):
- Confirm catalog
ai.versionis 1.9.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+projects→ Ask EloSync fetch project → propose status/assign/note → Confirm; status/note needprojects.update; assign needsprojects.assign.
Go-live: AI Project triage production readiness · ops AI deployment.
AI Purchase Order triage tools 1.9.0 → 1.10.0
After migrate (2026_09_15_100000_bump_ai_module_version_to_1_10_0):
- Confirm catalog
ai.versionis 1.10.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+purchase-orders→ Ask EloSync fetch PO → propose status/assign/note → Confirm; Draft→Sent needspurchase-orders.send; receive needspurchase-orders.receive; cancel needspurchase-orders.cancel.
Go-live: AI Purchase Order triage production readiness · ops AI deployment.
AI Payment triage tools 1.10.0 → 1.11.0
After migrate (2026_09_15_110000_bump_ai_module_version_to_1_11_0):
- Confirm catalog
ai.versionis 1.11.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+payments→ Ask EloSync fetch payment → propose post/void/assign/note → Confirm; Posted confirm callspost()(payments.post); Void confirm callsvoid()(payments.void); Draft target rejected.
Go-live: AI Payment triage production readiness · ops AI deployment.
AI Lead assign + note tools 1.11.0 → 1.12.0
After migrate (2026_09_15_120000_bump_ai_module_version_to_1_12_0):
- Confirm catalog
ai.versionis 1.12.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+leads→ Ask EloSync fetch lead → propose assign/note → Confirm; assign needsleads.assign; note needsleads.update.
Go-live: AI Lead assign + note production readiness · ops AI deployment.
AI Estimate triage tools 1.12.0 → 1.13.0
After migrate (2026_09_16_010000_bump_ai_module_version_to_1_13_0):
- Confirm catalog
ai.versionis 1.13.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+estimates→ Ask EloSync fetch estimate → propose status/assign/note → Confirm; Sent needsestimates.send; Accepted needsestimates.accept.
Go-live: AI Estimate triage production readiness · ops AI deployment.
AI Quotation triage tools 1.13.0 → 1.14.0
After migrate (2026_09_16_020000_bump_ai_module_version_to_1_14_0):
- Confirm catalog
ai.versionis 1.14.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+quotations→ Ask EloSync fetch quotation → propose status/assign/note → Confirm; Sent needsquotations.send; Accepted needsquotations.accept.
Go-live: AI Quotation triage production readiness · ops AI deployment.
AI Credit Note triage tools 1.14.0 → 1.15.0
After migrate (2026_09_16_030000_bump_ai_module_version_to_1_15_0):
- Confirm catalog
ai.versionis 1.15.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+credit-notes(Invoices required) → Ask EloSync fetch credit note → propose status/assign/note → Confirm; Issued needscredit-notes.issue; Draft target rejected.
Go-live: AI Credit Note triage production readiness · ops AI deployment.
AI Leave triage tools 1.15.0 → 1.16.0
After migrate (2026_09_16_040000_bump_ai_module_version_to_1_16_0):
- Confirm catalog
ai.versionis 1.16.0 (migrate-only; do notdb:seed). - No SPA/Mobile deploy required for this bump (confirm UI is tool-agnostic).
- Smoke: entitle
ai+employees+leave-management→ Ask EloSync list pending / fetch leave → propose approve or reject → Confirm; both writes needleave-management.approve; reject requires review notes.
Go-live: AI Leave triage production readiness · ops AI deployment.
Sales document invoice conversion (quotations 1.4.1 / contracts 1.2.1 / invoices 1.6.1 / estimates 1.3.3)
After migrate:
- Confirm catalog versions: quotations
1.4.1, contracts1.2.1, invoices1.6.1, estimates1.3.3 - Confirm
customer_invoices.contract_idexists (nullable FK) and unique nullableestimate_id - Confirm
quotations.convertandcontracts.convertare granted to default admin/manager roles - Deploy the SPA after migrate — convert actions 422 until Invoices is entitled; contract re-bills need
acknowledge_repeat_billing
Do not unique-index customer_invoices.quotation_id. Contract billing is repeatable. Quote/estimate one-shot uses QuotationInvoiceGuard plus lockForUpdate (and unique estimate_id).
Go-live: Sales document convert production readiness.
Sales document invoice conversion (quotations 1.4.0 / contracts 1.2.0 / invoices 1.6.0 / estimates 1.3.2)
Superseded by 1.4.1 / 1.2.1 / 1.6.1 / 1.3.3 integrity hardening above. Historical checklist:
- Confirm catalog versions: quotations
1.4.0, contracts1.2.0, invoices1.6.0, estimates1.3.2 - Confirm
customer_invoices.contract_idexists (nullable FK) - Confirm
quotations.convertandcontracts.convertare granted to default admin/manager roles - Deploy the SPA after migrate — convert actions 422 until Invoices is entitled on the workspace
Do not unique-index customer_invoices.quotation_id. Contract billing is repeatable; the quotation/estimate one-shot guard is application-level (QuotationInvoiceGuard).
Contracts 1.1.0 — auto-fill and HTML memos
After migrate:
- Confirm catalog
contractsversion is1.1.0 - Confirm
contracts.descriptionexists (nullable text) - Deploy the SPA after migrate — posting
descriptionbefore the column exists will 500 - Store allows the creating actor as
assigned_to. The SPA copies assignee only when that user is in the eligible picker; omittedassigned_tostill defaults to the actor.
See Contracts 1.1.0 production readiness.
Founding Beta invites
After migrate (2026_08_16_232506_add_founding_beta_invites_to_beta_applications_table):
- Confirm
founding_beta_enabled,founding_beta_apply_url, andfounding_beta_invite_ttl_daysexist (seeded viaupdateOrInsertin that migration). - Ensure a queue worker listens to
emails(Founding Beta invite notification). - Set
FRONTEND_URLto the SPA that serves/#/register?invite=…. - Keep marketing origins off
SANCTUM_STATEFUL_DOMAINS(public apply/resend use Bearer-less JSON + CSRF except).
Go-live checklist: Founding Beta invite production readiness.
Invoices 1.2.0 (status model)
After migrate:
- Confirm catalog
invoicesversion is1.2.0 - Confirm existing invoice rows remapped (
sent/partial→unpaid,void→cancelled)
Invoices 1.1.1 (branded PDF settings)
After migrate:
- Confirm catalog
invoicesversion is1.1.1 - Operators can fill Settings → Branding → Invoice company profile / payment details for PDF chrome
Invoices 1.1.0 (recurring + PDF)
After migrate:
- Confirm catalog
invoicesversion is at least1.1.0(prefer1.1.1after the branded PDF polish migrate) - Confirm scheduler includes
invoices:generate-recurring(daily,withoutOverlapping(120),onOneServer) composer installmust includedompdf/dompdffor PDF download- Optional env (defaults are fine):
INVOICES_RECURRING_CATCHUP_CAP(52),INVOICES_RECURRING_CHUNK_SIZE(100),INVOICES_RECURRING_TIME_BUDGET_SECONDS(45),INVOICES_PDF_CACHE_SECONDS(300),INVOICES_PDF_PER_MINUTE(30) - Treat a non-zero
invoices:generate-recurringexit as a tenant-series failure — see logsinvoices.generate-recurring.tenant_failed
See Invoices 1.1.0 production readiness.
Billing product line picker (quotations / estimates / invoices + products)
After migrate (Backend before SPA):
php artisan migrate --force
# includes:
# 2026_08_18_000658_bump_products_module_version_to_1_1_0
# 2026_08_18_001042_add_product_id_to_billing_document_lines_tables
# 2026_08_18_001043_bump_billing_document_modules_for_product_line_picker
# 2026_08_18_063000_bump_modules_for_product_line_picker_hardening- Confirm catalog versions: products
1.1.1, quotations1.3.1, estimates1.3.1, invoices1.5.1 - Existing entitled workspaces keep entitlements; catalog bump does not auto-install modules
- Smoke: create a line with
product_idon a draft quotation/estimate/invoice; convert estimate → invoice; generate a recurring occurrence
See Billing product line picker production readiness.
Storage module (2026-08-13)
After migrating Storage:
- Map gateway prices for each billable pack (
storage-10…storage-1000) × monthly/yearly - Confirm
FILESYSTEM_BRANDING_DISK=publicand uploads on S3/Wasabi - Workspaces that already had Team Chat receive free Storage automatically (grandfather migration)
- New workspaces install free Storage from Marketplace when they need content uploads
See Storage deployment.
New workspaces after upgrade
TenantProvisioningService continues to:
- Install every published
is_default_includedmodule - Provision default roles/permissions via
TenantAuthorizationProvisioningService - Create the owner via
TenantAuthBootstrapService(no RBAC mutation on later logins)
Multi-Provider Email Delivery
After pulling a release that includes multi-provider email:
composer install --no-dev --optimize-autoloader # pulls symfony/postmark-mailer + mailgun-mailer
php artisan migrate --force # email_logs tables + email-logs.* permission grants
php artisan email:migrate-tenant-mail-modes # optional; --dry-run first. Backfills mail_mode from legacy mail_host
php artisan queue:restart # required — workers cache mailer configNotes:
- Legacy tenants with a filled
mail_hoststill behave as custom SMTP until the migrate command runs (recommended). - Configure Central Settings → Mail (SMTP / Postmark / Mailgun). Env
MAIL_*remains bootstrap fallback only. - After changing mail credentials in Settings, always run
php artisan queue:restart. - Smoke: Central + Tenant Send test, then open Email logs.
See Multi-Provider Email.
Verification
php artisan test --compact tests/Feature/ProductionModuleDeploymentTest.phpSmoke:
- Existing workspace receives the new module in nav only if it never had that subscription (or already had it active)
- Customized role permission sets are unchanged except for explicitly migrated additive grants
- New registration still gets all default-included modules + full owner permissions
Expenses categories 1.1.0 (first-party SPA contract)
Ship Backend migrate before the SPA. Expense resources no longer return category as a string; the SPA expects category_id and embedded { id, name, slug }.
php artisan migrate --force # expense_categories + backfill + catalog bump 1.0.0 → 1.1.0
# then deploy Frontend, then DocsDo not run db:seed. Starter categories (Travel / Office / Software / Utilities / Other) lazy-seed on first list/create/PO convert. Other cannot be deleted; starter slugs stay stable if renamed.
See Expenses production.
Help Desk module v1.0.0
After migrate, existing workspaces do not auto-install Help Desk. Operators enable help-desk from Marketplace. Registration is migrate-only via DefaultModuleRegistrar — do not run db:seed. Permissions ship additively via TenantPermissionSynchronizer.
php artisan migrate --force # help_desk_* tables + catalog registration + help-desk.* permissions
# then deploy Frontend, then DocsSee Help Desk production.
Projects 1.0.0 + Tasks project_id (1.2.0)
php artisan migrate --force # projects tables + permissions + catalog + tasks.project_id + tasks 1.2.0
# then deploy Frontend (Projects nav + optional task project picker), then DocsDo not run db:seed. Projects is free Marketplace opt-in (not default-included). See Projects production.
Automation module (billable add-on)
After migrate, existing workspaces do not auto-install Automation. Operators install automation from Marketplace. Include the automations queue on workers and confirm automation:dispatch-schedules is on the scheduler. Optional env: AUTOMATION_WEBHOOK_SECRET. See Automation production.
Storage module + capacity packs
After migrate, free storage and billable packs are catalog-only except: workspaces that already have Team Chat entitled receive free Storage via the grandfather migration (and new Team Chat installs companion-install Storage). Map Stripe/Creem prices for each pack × cycle before selling packs. See Storage deployment.
Knowledge Base module (free Operations opt-in)
After migrate, existing workspaces do not auto-install Knowledge Base. Operators install knowledge-base from Marketplace (internal articles only; not billable). See Knowledge Base production.
Assets module v1.0.0
After migrate, existing workspaces do not auto-install Assets. Operators enable assets from Marketplace. Registration is migrate-only via DefaultModuleRegistrar — do not run db:seed. Permissions ship additively via TenantPermissionSynchronizer. No hard module dependencies.
php artisan migrate --force # assets / asset_notes / asset_activities + catalog registration + assets.* permissions
# then deploy Frontend, then DocsSee Assets production.