Upgrades and versioning
AiSOC follows Semantic Versioning on a single shared version across the monorepo. The authoritative version lives in VERSION; every release tag, container image, and SDK package is stamped with the same number.
This page is what you read before running git pull against a new release.
Release cadence
| Channel | Frequency | What's in it |
|---|---|---|
Patch (x.y.Z) | As needed, often weekly | Bug fixes, security patches, doc fixes. Always backwards compatible. |
Minor (x.Y.0) | ~Every 1–3 weeks | New connectors, new agents, new endpoints. Backwards compatible — your existing config keeps working. |
Major (X.0.0) | When breaking changes accumulate | Schema migrations that require downtime, removed endpoints, renamed env vars. |
Every release ships with a CHANGELOG.md entry that lists added features, behaviour changes, and any breaking notes. Read it before upgrading across a major version.
What "breaking" means in AiSOC
A change is breaking — and therefore lives in a major release — if any of these apply:
- An existing REST/GraphQL/WebSocket endpoint changes its request or response shape in a non-additive way.
- An environment variable is renamed or its parsing semantics change.
- A database migration cannot be rolled back without data loss.
- A connector schema changes such that previously valid
auth_configJSON would be rejected. - A built-in role's permission list is reduced (additions are not breaking).
- An SDK function signature changes in a non-additive way.
Adding new endpoints, new fields, new connectors, new permissions, or new optional env vars is not breaking and ships in minor releases.
Deprecation policy
When we plan to remove or change something:
- Announce in a minor release. The CHANGELOG calls out the deprecation, the runtime emits a warning log, and the OpenAPI spec marks the endpoint as
deprecated: true. - Keep working for at least one full major-version cycle. If we deprecate a behaviour in 6.3.0 and the next major is 7.0.0, the behaviour still works in every 6.x release.
- Remove in a major release. The CHANGELOG's "Breaking" section names the removal explicitly and links to the replacement.
If you depend on something marked deprecated, open an issue — sometimes we extend the window.
Before you upgrade
Run through this list every time:
- Read the CHANGELOG between your current version and the target. Pay attention to anything labelled "Breaking", "Migration", or "Action required".
- Back up your database.
pg_dumpof the AiSOC schema is the minimum bar. For Kafka- and ClickHouse-backed deployments, snapshot those too. - Snapshot your
AISOC_CREDENTIAL_KEY. If you lose it during the upgrade, every encrypted connector credential in the database becomes unrecoverable. Treat the key the same way you treat your database backup. - Confirm a maintenance window for the API service. Migrations run inside a single transaction where possible; minor releases typically take seconds, major releases can take minutes on large
audit_logtables. - Stage first. If you operate a non-production tenant on the same code as production, upgrade it first and let it run for a day before promoting.
In-place upgrade procedure
The supported upgrade path is "stop, pull, migrate, start". Rolling upgrades across multiple API replicas are safe within a minor release; cross-major rolling upgrades are not supported because the running code may not understand the migrated schema.
# 1. Park new traffic at the ingress (return 503 to the API service).
# 2. Stop the API service replicas. Connectors and ingest can keep running —
# they tolerate a temporary API outage and back-pressure into Kafka.
cd /opt/aisoc # your install path
git fetch --tags
git checkout v7.3.1 # the target tag
# 3. Pull dependencies.
pnpm install --frozen-lockfile
(cd services/api && uv sync)
# 4. Run database migrations.
# From v7.3.1 onwards you can use the CLI directly:
aisoc db upgrade
# (Equivalent to: cd services/api && uv run python -m app.scripts.run_migrations)
# Set AISOC_MIGRATIONS_STRICT=1 so a failed migration aborts. Without it the
# runner logs the failure, rolls that statement back, continues, and exits 0 —
# which ships a partially-applied schema silently.
# 5. Start the API service back up.
docker compose -f docker-compose.yml -f infra/compose/docker-compose.dev.yml up -d api
# 6. Verify health and remove the maintenance gate.
curl -fsS http://localhost:8000/healthz
The v7.3.1 release made the close-to-7.x migrations (005_compliance.sql,
025_connectors_click_and_connect.sql, and the new
042_alerts_schema_drift_fix.sql) idempotent. If a previous upgrade left
your alerts table partially migrated, just re-run aisoc db upgrade —
the migration only adds columns that are missing instead of failing on
already-present ones. See the CHANGELOG
for the full column list.
For Kubernetes deployments, the same flow applies: scale the API deployment to zero, run the migration as a Job, then scale back up. The Helm chart in infra/helm/ exposes this as helm upgrade --set runMigrations=true.
Verifying the upgrade
After a successful upgrade you should be able to:
- Hit
/healthzand/readyzand get200 OKwith nodegradedservices in the body. - Run
aisoc-cli benchmark(orpnpm aisoc:benchmark) and see the same or better numbers as before. - Open the analyst console and see the version footer match the new tag.
- Check
/api/v1/system/versionand see the same number.
If any of those fail, see Troubleshooting. Note what rollback does and does not mean here: services/api uses a forward-only SQL migration runner (services/api/app/scripts/run_migrations.py), not Alembic — there is no alembic.ini, no alembic/ directory, and no downgrade concept. The only services with Alembic are ueba, honeytokens, purple-team and osquery-tls. Reverting the code to the previous tag is supported; reverting the schema is not, so a rollback that needs the old schema means restoring the snapshot taken in step 2 of the pre-upgrade checklist.
Rolling back
Patch and minor releases are designed to roll back cleanly. The procedure mirrors the upgrade:
git checkout v7.3.0 # the previous tag
pnpm install --frozen-lockfile
(cd services/api && uv sync)
# No schema downgrade step: the API migration runner is forward-only.
# Additive migrations (the common case) are backward compatible, so the previous
# code runs against the newer schema. If a migration was destructive, restore the
# snapshot instead — the CHANGELOG flags those as irreversible.
docker compose -f docker-compose.yml -f infra/compose/docker-compose.dev.yml up -d api
Major releases occasionally ship one-way migrations (e.g. column drops). When that's the case, the CHANGELOG flags the migration as "irreversible" and the only rollback is restoring from the database snapshot you took in step 2 of the pre-upgrade checklist.
Upgrading across a major
Every major below changes behaviour rather than only schema, so read the row
for each one you are crossing. The authoritative list of what breaks is the
### BREAKING section of CHANGELOG.md;
this table says what an operator has to do.
| From, to | What changes | What you do |
|---|---|---|
| any, v10.0.0 | Services stop connecting to Postgres as a superuser, so the 92 row-level security policies begin to apply. The four services running their own alembic chain migrate as the owner, not as the app role. | Run the migrations with DATABASE_MIGRATION_URL set to the owning role. A service that still connects as a superuser bypasses every policy, so check the role before and after. |
| v10, v11.0.0 | POST /v1/ingest and /v1/ingest/batch require a credential. CORE needs 8 GB of memory and 20 GB of disk, up from about 6.5 GB. | Mint an ingest token and set it on every producer before upgrading, or ingestion stops. Check the host has the headroom first. |
| v11, v12.0.0 | The actions service returns 503 on every mutating route, and the realtime edge rejects every connection, until their required secrets are set. | Set them before you start the stack. Both failures are deliberate and loud. |
| v12, v13.0.0 | 75 state-changing routes require a permission they did not require before, and several move to settings:write, which soc_analyst, soc_lead and threat_hunter do not hold. | Expect HTTP 403 where operators previously got 200. Review who needs settings:write before upgrading, not after. |
| v13, v14.0.0 | No route will confer a role, scope or organisation membership beyond the caller's own authority, and platform_admin and admin are unreachable from every API route. | The only way to mint a wildcard role is python -m app.scripts.bootstrap_admin against the database. Any automation that created one through the API stops working. |
| v14, v15.0.0 | /api/v1/shifts is removed and /api/v1/threatintel/stix/* reads answer 404 outside demo mode. make up starts a production-class stack and AISOC_DEV_MODE no longer defaults to on. | Delete any caller of those routes; neither served data anyone entered. Set ENVIRONMENT=development explicitly if you were relying on the dev auth bypass, and expect previously silent warnings to become boot refusals. |
| v15.0.0, v15.1.0 | Nothing breaks — two routes are added and none removed. Two behaviours change noticeably all the same: CloudTrail deployments will see more alerts, because every event previously collapsed onto a single one; and alert-triggered playbooks now actually run, where find_matching() had no production caller before. | No action required to upgrade. Expect the CloudTrail alert volume you should have been getting all along, and review which playbooks are enabled before upgrading, since a playbook that never fired will now fire — in preview by default, with three opt-ins needed before it can act. |
| v15.1.0, v16.0.0 | The cases table is gone. Migration 083 moves its rows into aisoc_cases, repoints the child foreign keys, and renames the old table to cases_pre_consolidation so nothing is lost. The two tables never synchronised: the console wrote one and every case metric read the other, so MTTR and the case counts were blind to every case an analyst created. Separately, SAML and OIDC sign-in now works — it returned 403 on every deployment because the connection table had no writer. | Check your own SQL. Anything querying cases — a Grafana panel, a scheduled export, a report — must read aisoc_cases. It will fail loudly with "relation does not exist" rather than returning stale rows, which is deliberate. The archive table is readable if you need to compare. Expect case metrics to change the moment you upgrade: they were under-reporting, not over-reporting. If you use SSO, create a connection at POST /api/v1/sso-connections before pointing users at it. |
| v16.0.0, v16.0.1 | Nothing breaks. A security release closing a high-severity privilege escalation (GHSA-4gx4-x7gm-4xq8): the check deciding whether a caller may confer authority read the caller's static role, while the check admitting it to the route read its database-resolved permissions. Where a tenant uses the roles and user_roles tables to narrow an account, those are different answers, and the narrowed account could grant itself anything its unnarrowed role carried. | Upgrade if you restrict accounts through database-backed RBAC. A deployment with no RBAC rows was never exposed — the static map was already the correct answer for it. After upgrading, a grant is refused when it exceeds what the caller actually holds, so an operator who had been relying on the wider static role to assign roles will now get HTTP 403; grant that operator the permissions through RBAC rather than widening its static role. |
| v16.0.1, v17.0.0 | Three routes are removed: POST /detection-loop/suggest and the two GET /detection-loop/suggestions reads, with their three schemas. No migration is needed because none of them ever worked — they queried aisoc_alerts, aisoc_detection_rules and alerts.evidence, none of which any migration creates, so every caller was already receiving an error. The governed equivalent is POST /api/v1/detection-proposals. One number changes meaning without erroring, which is the one to read twice: alerts.total on /api/v1/metrics/dashboard now counts open work — new, triaging, in_progress — rather than every alert ever received, and the severity counts beside it are scoped the same way. A panel charting cumulative intake from that field will drop to the size of your queue. Closed work is reported separately as alerts.resolved. Migration 090 adds three nullable columns to aisoc_cases; it is additive and runs automatically. |
Upgrading more than one major at a time is supported but untested as a single step. Do them one at a time, running migrations between each, so that a failure names the version that caused it.
Version skew
Within a given major version, the following components are guaranteed to be wire-compatible across one minor version of skew:
- Browser ↔ API service
- API service ↔ Connectors
- API service ↔ Agents
- SDK clients (Go, Python, TypeScript) ↔ API
That means you can upgrade the API service to 6.1.0 while connectors are still on 6.0.x, finish their rollout over the day, and not break anything. Across a major version (6.x ↔ 7.x) the contract resets — upgrade the API first, then everything else, on the same maintenance window.
Long-Term Support
AiSOC does not currently offer formal LTS releases. The most recent major version is the supported version; security patches and CVE fixes are backported to the previous major for 90 days after a new major lands, which is the window we expect operators to need to plan and execute their upgrade.
If your environment requires a longer support window, raise it in Discussions — we're happy to discuss commercial support arrangements with the community.
Pre-1.0 history
Versions 1.0.0 through 5.x shipped during the original feature build-out and are documented in the CHANGELOG. The 6.0.0 release in May 2026 was the first version we consider production-ready; new deployments should start from the latest tagged release (currently v7.3.1).