---
title: Changelog
path: changelog
status: published
---

# Changelog

Notable changes by milestone. ScaiControl uses migration numbers (`001`–`022+`) as its versioning anchor; release tags follow the migration that bumped the schema.

## v1.x (current)

### Outbound webhooks (migration 022)

Two-table durable event-outbox pattern. 14 lifecycle topics ship in v1.0:

- `tenant.billing_linked.v1`, `tenant.billing_updated.v1`
- `partner.billing_linked.v1`, `partner.billing_updated.v1`
- `subscription.activated.v1`, `subscription.changed.v1`, `subscription.cancelled.v1`, `subscription.suspended.v1`, `subscription.resumed.v1`, `subscription.trial_ending.v1`, `subscription.payment_failed.v1`
- `pack_subscription.activated.v1`, `pack_subscription.changed.v1` (reserved), `pack_subscription.cancelled.v1`

HMAC-SHA256 signing, 1m/5m/30m/2h/12h/24h backoff schedule, `(subscription_id, idempotency_key)` uniqueness, admin UI at `/admin/webhook-subscriptions`. See [Concepts: webhooks](./concepts/webhooks).

### Email template system (migration 020)

Operator-customisable email templates per `(name, document_type, language)` triple. GrapesJS designer reused from invoices; Jinja syntax encoded as HTML comments (`<!--JS:if-->`) so the designer doesn't choke on `{% %}` tags. Variables differ per document_type (invoice-sent vs trial-ending vs payment-failed). Validated by `scaicontrol admin templates validate`.

### Service packs (migration 014)

Bundled subscriptions modeled as a parent `PackSubscription` linked to child `Subscription` rows. Pricing can be `fixed_price` (override the sum) or `percentage` (discount off the catalog sum). Lifecycle propagates parent → children atomically. Pack-level events fire instead of per-child events. See [Concepts: service packs](./concepts/service-packs).

### Identity sync (migration 013)

`scaicontrol sync` pulls users + groups from ScaiKey, merging with local role assignments. Resolved the "super-admin downgrade after JWT refresh" issue: ScaiControl now re-fetches the canonical permission set from `/auth/me` on each token refresh.

### Template designer (migration 012)

GrapesJS-based WYSIWYG editor for invoice + credit-note templates, including a live preview pane that renders the same WeasyPrint pipeline used for production PDFs. Templates support partner-specific overrides AND language fallbacks. Blogger Sans is embedded as base64 so WeasyPrint doesn't need to fetch fonts at render time.

### Accounting integrations (migration 011)

Plugin architecture for outbound accounting sync. First implementation: Visma e-Accounting (Sweden/Norway/Finland). Hooks fire on invoice finalize → mirrored to the external accounting system. Failures don't roll back the invoice — they queue for retry.

### EU-compliant invoicing (migration 008)

The biggest milestone. Adds:

- `tenant_billing_profiles` — buyer EU address + VAT.
- `partner_configuration` extensions — seller EU address + VAT + bank.
- `invoice_templates` table with partner-specific and language-specific overrides.
- `invoices.document_type` (`invoice` | `credit_note`) and `invoices.referenced_invoice_id`.
- Buyer + seller + VAT-details snapshots frozen on finalize.
- VAT determination (`services/billing/vat.py`): 4 rules covering domestic, intra-EU B2B reverse-charge per Art. 196 EU VAT Directive, intra-EU B2C, and non-EU export.
- WeasyPrint-rendered PDFs in S3 keyed by invoice ID; `template_html_snapshot` for byte-stable reprints.
- `CreditNoteSequence` for separate gap-free `SCAI-CN-YYYY-NNNNNN` numbering.
- E-invoicing in migration 009 (UBL / XRechnung / CII / ZUGFeRD / Factur-X).
- Per-line tax in migration 010 (replacing single invoice-level rate).

See [Concepts: invoice lifecycle](./concepts/invoice-lifecycle), [Concepts: VAT & reverse charge](./concepts/vat-and-reverse-charge).

### Cost-based metering (migration 007)

Cost-derived plans where the unit price is computed from a per-unit cost + margin instead of fixed. Subscriptions inherit a billable rate updated on plan refresh; usage records reference the rate at ingestion time.

### Service registry (migration 006)

Parent-slug relationships for service hierarchy (e.g. `scaicontrol-billing` is a sub-component of `scaicontrol`). Health monitoring split from approval state — `health_status` is now derived continuously from heartbeats independent of `registration_status`.

### Users + groups (migrations 003–005)

Identity reflection from ScaiKey: `users`, `groups`, and `user_groups` populated by `scaicontrol sync`. Role bundles defined locally on groups; effective permissions = union of (user.role scopes ∪ groups.role scopes).

### Marketplace (migration 002)

Service catalog: `services`, `plans`, foundational subscription tables.

### Initial schema (migration 001)

Tenancy hierarchy (partners, tenants, users), audit log, basic ORM-layer tenant filter via `do_orm_execute`.

## Forward-looking

Topics deferred past v1.x — these features exist conceptually but have no producer wired up yet:

- **Invoice events** — `invoice.issued`, `invoice.paid`, `invoice.overdue`. Will land when ScaiCRM needs them for AR automation.
- **Dunning escalation events** — surfacing the past_due → suspended path to subscribers.
- **Usage aggregate events** — daily and monthly usage roll-ups as topic streams.
- **Catalog product events** — when a plan price changes, when a service is added/retired.
- **Provisioning rejection events** — explicit signal when a DAG step's compensating rollback completes.
- **Plan-change service tokens** — service-to-service tokens with a `scaicontrol:admin` scope for CRM-driven plan changes.

## Versioning policy

- ScaiControl's external API is versioned at the URL (`/api/v1/`). Within v1, only additive changes ship — no field removals, no semantics shifts.
- Outbound event topics are versioned at the topic name (`subscription.activated.v1`). Breaking shape changes ship a new major (`v2`); both versions coexist for at least one full quarter before `v1` retires.
- Migrations are numbered sequentially and never re-numbered. Once a migration is in `main`, it's append-only — corrections ship as later migrations.
