SOIT Docs
Operations

Migration and Upgrade

Plan SOIT upgrades with database migrations, compatibility checks, per-version upgrade notes, and rollback evidence.

SOIT upgrades should preserve runtime data, version records, and workspace isolation. All schema changes ship as reviewed Alembic migrations — raw SQL is never applied directly in production.

Preflight

Run from soit/server before any upgrade:

uv sync
uv run alembic heads
uv run alembic history --verbose

Expected before proceeding:

  • alembic heads reports exactly one release head.
  • The target release contains no unreviewed migration files.
  • Release notes list the migration range and any contract or schema changes.
  • Workers can be stopped or drained if required.

Backup

Before upgrading any database with meaningful data:

  1. Export PostgreSQL with a timestamped dump.
  2. Record the current alembic_version.
  3. Export .env or deployment environment settings.
  4. Snapshot object storage, vector store, and Vault data when the environment contains real data.
  5. Record the exact SOIT commit, Docker image tag, and operator.

Fresh installation path

Proves a new environment can initialize the schema from scratch:

cd server
uv run alembic upgrade head
uv run alembic current
uv run python scripts/bootstrap_admin.py
uv run python scripts/bootstrap_enterprise_mvp.py

alembic current should report the same revision as alembic heads, and the bootstrap plus demo seed should run cleanly afterwards.

Existing database path

Proves an existing database upgrades in place without destructive reset:

cd server
uv run alembic current      # record the pre-upgrade revision
uv run alembic upgrade head
uv run alembic current      # must now match heads
uv run pytest tests/integration/test_enterprise_agent_mvp.py -q

After the upgrade, existing tenant, workspace, and user rows must remain readable and the demo path must still run. No manual data-directory cleanup or destructive reset should be needed.

Version upgrade notes

Each release supports upgrading in place from the previous minor release (its N-1 range). For every version: back up the database and object storage, run alembic upgrade head (the compose migrate service does this), then restart the API, the workers, and the web console on the new images.

v1.7.0

ItemValue
Fresh head20261003100000
Migrations added20261003100000
N-1 range20260927160000..20261003100000: a v1.6.x database (20261002120000) upgrades in place
New settingsNone; keep APPROVAL_SWEEPER_ENABLED=true on the API service
  • 20261003100000 adds assignee_user_ids, assignee_roles, and expires_at to approval_requests, and the approval_decisions table. Existing requests stay unassigned and never expire.

The migration has a downgrade. Downgrading to 20261002120000 drops the assignees, the deadlines, and the whole decision history. Restoring the backup taken before the upgrade is the preferred way back.

Behaviour changes to review:

  • A decision on a workflow approval resumes the run on the server. A client that resumes such runs itself may now get 409 because the run is already resuming; treat it as done.
  • The approval sweeper also expires overdue requests; an expired request counts as a rejection.
  • Owners and Admins are notified of approval requests nobody is assigned to (category task); members can switch the category off in their notification preferences.
  • Approval responses carry can_decide, can_cancel, and assigned_to_me, and approval events carry the request's requester, assignees, and deadline; approval.delegated is a new event.
  • Once a ToolSpec declares approvers or timeout_seconds, earlier versions refuse it; do not roll back past v1.7.0 after using them.

v1.7.0 includes the approval fixes of v1.6.1. See Version 1.7.0 and Approvals.

v1.6.1

An approval fix on v1.6.0; upgrade every installation that gates tool calls on approval. It adds no migrations (fresh head and N-1 range as v1.6.0), so restart the API and workers on the new images. Enable the approval sweeper on the API service if you do not use the shipped compose files: APPROVAL_SWEEPER_ENABLED=true (and optionally APPROVAL_SWEEPER_INTERVAL, 30 seconds by default). Its first sweep closes the pending requests of runs that already ended.

Fixed:

  • Resuming a workflow run that stopped for approval skipped the tool node's approval gate, so a call a reviewer rejected, or nobody had decided yet, ran once anyone allowed to run the workflow resumed it. Resuming now reads the decision: pending or missing answers 409; rejected, canceled, or expired ends the node as APPROVAL_REJECTED without calling the tool; only approved runs it.
  • Two decisions on one request could both be taken. The request is now locked while it is decided: the same decision again returns it unchanged, and a different one on a closed request answers 409 (it was 400).
  • Canceling a request now sends approval.canceled, which releases a waiting task as a rejection does (approval_canceled), and the sweeper closes pending requests whose run has ended.

To find tool calls that ran without an approval before the fix, on PostgreSQL:

SELECT a.run_id, a.id AS approval_id, a.status AS approval_status, t.tool_ref
FROM approval_requests a
JOIN run_step_tool_calls t
  ON t.run_id = a.run_id
 AND t.tool_call_id = a.details_json ->> 'tool_call_id'
WHERE a.status <> 'approved'
  AND t.status = 'succeeded';

v1.6.0

ItemValue
Fresh head20261002120000
Migrations added20261002100000, 20261002110000, 20261002120000
N-1 range20260927160000..20261002120000: a v1.5.x database (20261001110000) upgrades in place
New settingsNone
  • 20261002100000 adds upstream_id and upstream_request_id to run_cost_entries, nullable and not unique.
  • 20261002110000 adds unpriced_call_policy to workspaces; existing workspaces get allow.
  • 20261002120000 adds knowledge_document_restrictions; nothing is restricted until someone restricts it.

All three migrations have a downgrade. Downgrading to 20261001110000 drops the provider ids, every workspace's unpriced call policy, and every document restriction, so restricted documents become readable to everyone their knowledge base admits. Restoring the backup taken before the upgrade is the preferred way back.

Behaviour changes to review:

  • Evidence bundles and ledger exports declare ledger contract 1.1. Cost records may carry upstream_id and upstream_request_id; a consumer that rejects unknown fields must accept them.
  • Copies kept in runs, responses, threads, and tasks now follow the reader's current access to knowledge, including the visibility rules that existed before: a member who cannot read someone else's private knowledge base, or a base not shared with their workspace, no longer sees its documents in the runs of those who could. Items are dropped from lists, totals recounted, and other copies replaced by a withheld marker.
  • The keyword fallback of retrieval answers from the latest version of each document only, as the vector index does.
  • Workflow steps record the documents their output named in a knowledge_documents metric.
  • The Overview spend tile and the dashboard's run costs no longer add currencies together: a run priced in more than one currency has no single dashboard cost, and run detail totals each currency apart. Unpriced entries add nothing, so a run with only those shows a dash, not zero.

v1.6.0 includes the security fixes of v1.5.2. See Version 1.6.0.

v1.5.2

A security patch on v1.5.1; upgrade every v1.5.x installation. It adds no migrations (fresh head and N-1 range as v1.5.0), so restart the API and workers on the new images. Behaviour changes to review:

  • Granting and revoking resource access (POST and DELETE /api/v1/resource-grants) takes a workspace Owner or Admin. A member with the Developer role who managed grants under Govern › Access now gets 403.
  • A knowledge document's file_id must name a file under the same knowledge base's storage prefix; any other is refused with 400. A client that uploaded documents with a file_id of its own must upload the file instead, or name a file already stored for the same knowledge base.
  • Review the audit ledger for grants made by members who were not Owners or Admins (identity.resource_grant.changed, its actor), and revoke any that should not exist.

v1.5.1

No migrations, and no server change apart from the version: only the command line's distribution changed. The CLI is published to PyPI as soit-cli; a command line installed from the repository can be replaced with uv tool install soit-cli. See SOIT CLI.

v1.5.0

ItemValue
Fresh head20261001110000
Migrations added20261001100000, 20261001110000
N-1 range20260927160000..20261001110000: a v1.4.0 database (20260927190000) upgrades in place
New settingsKNOWLEDGE_SYNC_* in server/.env.example; defaults need no change
  • 20261001100000 adds regression_datasets and regression_dataset_versions, creates one dataset (with one version holding its active cases) for every agent and dataset name that already has cases, and makes regression_cases.source_run_id nullable.
  • 20261001110000 adds knowledge_sources, knowledge_source_items, and knowledge_sync_runs.

Both migrations have a downgrade. Downgrading to 20260927190000 drops the datasets, their versions, and every knowledge source with its items and sync runs; documents already ingested stay. A case written without a source run gets an empty source_run_id. Restoring the backup taken before the upgrade is the preferred way back.

Knowledge connector syncs run in the knowledge ingest worker and are on by default (KNOWLEDGE_SYNC_WORKER_ENABLED=true). The KNOWLEDGE_SYNC_* settings hold the poll interval, concurrency, schedule spacing, and per-run size caps. A source on a private network needs that network in EGRESS_PRIVATE_NETWORKS.

Behaviour changes to review:

  • Retrying a task of a type nothing can re-execute is refused with TASK_NOT_RERUNNABLE (the task_type in the details) instead of a generic 409 CONFLICT, and the task keeps its terminal status.
  • /api/v1/images/* results name the run artifact as artifact_id; attachment_id still carries the same id.
  • A virtual model's image call moves on to its next target when a target cannot carry an option the call asks for, instead of being refused by the first available target.
  • Azure AI MAI image edits refuse n and size before the call.
  • Passwords are checked with bcrypt 5 directly. Stored hashes verify unchanged, and a password past 72 bytes is cut there as before.
  • Every timestamp a model reads is an aware UTC datetime, and a model refuses a naive datetime. Extension packages that write naive datetimes through SOIT's models must pass aware ones.
  • The agent detail page no longer shows inputs that saved nothing (trigger, output schema, governance preview, alert threshold, rate limit, retry, and on-failure settings).
  • The pre-rebuild console routes (web/app/routes_old/) are removed.

See Version 1.5.0.

Rollback

Alembic downgrade support is migration-specific and should not be assumed for real data. The supported rollback plan:

  1. Stop API, worker, and web processes.
  2. Restore PostgreSQL from the pre-upgrade dump.
  3. Restore object-storage, vector, and Vault snapshots if the failed migration touched related state.
  4. Restore the previous image tags or commit.
  5. Run uv run alembic current and the relevant smoke test.

If a migration has a verified downgrade() path, exercise it in a disposable staging environment before relying on it operationally. Treat breaking contract changes as release events that need explicit migration and recovery planning — see Versioning and Release.

On this page