ADR-0072: Each module owns its tables; the schema history is re-baselined once, per module
- Status: accepted; amended by ADR-0101 (plugins own a schema, not tables in
public) - Date: 2026-09-13
- Deciders: Mahdi Amirabdollahi
- Supersedes: the history-continuity part of ADR-0008 (Liquibase, XML master and SQL changesets remain)
- Depends on: ADR-0069
Context
Twenty-five changesets live in one changes/ directory, grouped by date rather than owner. Several concerns span non-adjacent files: identity in 003/014, alerting in 006/013/020, request-reply in 007/011/017/020. File 020 alters three concerns at once.
Every entity and repository sits in one persist/ package, and audit_event has foreign keys to app_user, cluster and broker_node. Removing a cluster sets its audit rows' cluster_id to null, erasing the attribution the audit trail exists to keep.
Liquibase identifies a changeset by id, author and file path. Moving a released file to a module directory therefore makes Liquibase see it as new. Existing installations would re-run it, or need logicalFilePath shims on every moved file.
Studio is pre-stable. Every release is a :dev pre-release, and no stable tag exists.
Decision
- Ownership. Every table belongs to exactly one module. Only that module's
internal.persistencepackage holds its entities, repositories and JDBC access. Other modules read through the owner'sapi. - Layout.
db.changelog-master.xmlincludesdb/changelog/<module>/changelog.xmlin topological order: kernel core, security, audit, settings; then clusters, scrape, apitokens, events, alerting, rr, sql, brokerconfig.- Each module changelog uses
includeAll path="changes/" relativeToChangelogFile="true".
- One-time re-baseline.
changes/001–025are replaced by one0001-baseline.sqlper module, generated from a database migrated by the old history and split by owner.- Column order (non-negotiable #7), storage parameters, partitions, full-text configuration and seeds are preserved.
- A schema-diff test fails on any difference except the deliberate ones below.
- Deliberate differences.
oidc_role_mappingbecomesidentity_group_mappingkeyed by provider (ADR-0073).app_usergainsprovider_idandexternal_subject.audit_eventdrops its three foreign keys and gainscluster_name, so an audit event outlives the user, cluster or node it names.
- Cross-module foreign keys must follow an allowed module dependency.
SchemaOwnershipTestenforces this. - Disabled features still migrate, so enabling one never needs a migration.
- Released changesets stay immutable. This re-baseline is the single recorded exception, taken while no stable release exists.
Consequences
- BREAKING: an existing database cannot be upgraded. Operators recreate the database and re-register clusters, users, roles, tokens, channels, rules, capture subscriptions and configuration declarations. The release notes carry the steps.
- Rolling back means the previous image plus a database restored from a pre-upgrade backup.
- A module's schema is readable in one directory, and adding a module adds a directory.
- Audit history survives cluster and user removal.
- Changesets written after the baseline are again never edited once released.
Alternatives considered
- Freeze 001–025 in place and add new changesets per module. Existing installs keep upgrading, but ownership would be split forever between a historical blob and module directories. Rejected by the maintainer in favour of a clean layout while the project is pre-stable.
- Move the files with
logicalFilePathpreserved. Ownership would be readable, but every moved file would carry a path it no longer lives at, and 020 still spans three owners. - Schema per module (Postgres schemas). Cross-module reads and the existing JPA mappings would need qualification everywhere for no operational gain.