openvibers/openvibe.contracts Commits
ADR-028: expand, migrate, contract — schema changes survive a rollback to any release of the last 7 days; destructive statements need a dated contract step; a rollback-with-newer-writes test (WS-P tasks 12, 13)
Changes
nnew file mode 100644@@@ -0,0 +1,67 @@++# ADR-028: Expand, migrate, contract — schema changes that survive a rollback++++**Status:** Accepted 2026-09-25; enforced first in OpenVibe.Live (`test/destructive-migrations.test.js`,++`test/rollback-newer-writes.test.js`). Roadmap WS-P tasks 12 and 13.++++## Context and current evidence++++Every service keeps its state in SQLite and changes its schema at boot (`CREATE … IF NOT EXISTS`,++`ADD COLUMN`, and ledgered data migrations such as Live's `server/db/migrations.js`). Production rolls++back by switching the release (Live's release layout, `ovhost deploy`), never by restoring the database:++the older release starts on the database the newer one migrated and wrote to. A change that removes or++renames a table or column, or rewrites a value into a shape the older code cannot read, therefore makes++the rollback fail exactly when it is needed. Live's history has five one-time table rebuilds (copy every++column into `<table>_new`, drop, rename) that were safe because they kept every column, but nothing++checked that.++++## Decision++++A schema change is made in up to three releases, each deployable and each rollback-safe on its own:++++1. **Expand** — add only: new tables, new nullable or defaulted columns, new indexes. Old and new code++ both work on the result.++2. **Migrate** — write both shapes and backfill (a ledgered migration: transactional, idempotent,++ `adopt`/`DEFER` where needed). Reads move to the new shape. Still nothing is removed.++3. **Contract** — remove or rename (DROP TABLE, DROP COLUMN, RENAME COLUMN, RENAME TO), and stop writing++ the old shape, only after a release in which nothing reads it, with a backup taken first. A contract++ step is a dated entry in the plan's calendar (section 10), and it is listed with that entry where the++ service's check requires it.++++The supported rollback window is **7 days**: any release of the last week must boot and work on today's++database.++++**Checks.** Each service with a schema adds, as it adopts this ADR:++++- a **destructive-statement check**: a `DROP TABLE`, `DROP COLUMN`, `RENAME COLUMN` or `RENAME TO` of a++ real table (temporary `_x`, `x_new`, `x_old`, `x_tmp` excluded) fails the suite unless an allowlist++ names it with its contract step (Live: `test/fixtures/destructive-migrations.json`);++- a **rollback-with-newer-writes test**: the release of 7 days ago, checked out into a temporary++ worktree, initialises on a database the current code migrated and wrote to, reads and writes, and the++ current code reads what it wrote (Live: `test/rollback-newer-writes.test.js`). A shallow CI clone has no++ history, so the test skips there and runs in full wherever history exists (locally, and in deploy++ checks).++++## Alternatives considered++++- **Down migrations.** Rejected: a rollback that rewrites the database loses what the newer release++ wrote, and SQLite schema changes are not cheaply reversible. The older code must simply tolerate the++ newer schema.++- **Restore the database on rollback.** Rejected for the same reason: it throws away newer writes (money,++ chat, uploads).++++## Migration consequences++++Existing destructive statements are recorded, not rewritten. Upcoming contract steps already in the++calendar (Live's `users.email`/`password_hash` columns in WS-B task 2, C-73's table drop, C-75's boot++backfill) follow this ADR: each runs after a release with no reads, with a backup.++++## Rollback++++The checks are tests; removing a test file reverts them. The rule itself costs one extra release per++destructive change.++++## Acceptance tests++++- Live: `test/destructive-migrations.test.js` fails on a new unlisted `DROP COLUMN` (probed 2026-09-25);++ `test/rollback-newer-writes.test.js` passes with the release of 2026-09-18 on a database migrated by++ 2026-09-25's code.@@@ -31,3 +31,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational || [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) || [ADR-027](ADR-027-tools-platform-api.md) | Tools platform API: descriptors, one run API, caller tiers, probes, abuse log, legacy sunset | Accepted 2026-09-23; contracts v0.33.0, routes planned |++| [ADR-028](ADR-028-expand-migrate-contract.md) | Expand, migrate, contract: schema changes that survive a rollback (7-day window); destructive-statement check and rollback-with-newer-writes test | Accepted 2026-09-25; enforced in Live |
new file mode 100644
new file mode 100644
@@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
+# ADR-028: Expand, migrate, contract — schema changes that survive a rollback
+
+**Status:** Accepted 2026-09-25; enforced first in OpenVibe.Live (`test/destructive-migrations.test.js`,
+`test/rollback-newer-writes.test.js`). Roadmap WS-P tasks 12 and 13.
+
+## Context and current evidence
+
+Every service keeps its state in SQLite and changes its schema at boot (`CREATE … IF NOT EXISTS`,
+`ADD COLUMN`, and ledgered data migrations such as Live's `server/db/migrations.js`). Production rolls
+back by switching the release (Live's release layout, `ovhost deploy`), never by restoring the database:
+the older release starts on the database the newer one migrated and wrote to. A change that removes or
+renames a table or column, or rewrites a value into a shape the older code cannot read, therefore makes
+the rollback fail exactly when it is needed. Live's history has five one-time table rebuilds (copy every
+column into `<table>_new`, drop, rename) that were safe because they kept every column, but nothing
+checked that.
+
+## Decision
+
+A schema change is made in up to three releases, each deployable and each rollback-safe on its own:
+
+1. **Expand** — add only: new tables, new nullable or defaulted columns, new indexes. Old and new code
+ both work on the result.
+2. **Migrate** — write both shapes and backfill (a ledgered migration: transactional, idempotent,
+ `adopt`/`DEFER` where needed). Reads move to the new shape. Still nothing is removed.
+3. **Contract** — remove or rename (DROP TABLE, DROP COLUMN, RENAME COLUMN, RENAME TO), and stop writing
+ the old shape, only after a release in which nothing reads it, with a backup taken first. A contract
+ step is a dated entry in the plan's calendar (section 10), and it is listed with that entry where the
+ service's check requires it.
+
+The supported rollback window is **7 days**: any release of the last week must boot and work on today's
+database.
+
+**Checks.** Each service with a schema adds, as it adopts this ADR:
+
+- a **destructive-statement check**: a `DROP TABLE`, `DROP COLUMN`, `RENAME COLUMN` or `RENAME TO` of a
+ real table (temporary `_x`, `x_new`, `x_old`, `x_tmp` excluded) fails the suite unless an allowlist
+ names it with its contract step (Live: `test/fixtures/destructive-migrations.json`);
+- a **rollback-with-newer-writes test**: the release of 7 days ago, checked out into a temporary
+ worktree, initialises on a database the current code migrated and wrote to, reads and writes, and the
+ current code reads what it wrote (Live: `test/rollback-newer-writes.test.js`). A shallow CI clone has no
+ history, so the test skips there and runs in full wherever history exists (locally, and in deploy
+ checks).
+
+## Alternatives considered
+
+- **Down migrations.** Rejected: a rollback that rewrites the database loses what the newer release
+ wrote, and SQLite schema changes are not cheaply reversible. The older code must simply tolerate the
+ newer schema.
+- **Restore the database on rollback.** Rejected for the same reason: it throws away newer writes (money,
+ chat, uploads).
+
+## Migration consequences
+
+Existing destructive statements are recorded, not rewritten. Upcoming contract steps already in the
+calendar (Live's `users.email`/`password_hash` columns in WS-B task 2, C-73's table drop, C-75's boot
+backfill) follow this ADR: each runs after a release with no reads, with a backup.
+
+## Rollback
+
+The checks are tests; removing a test file reverts them. The rule itself costs one extra release per
+destructive change.
+
+## Acceptance tests
+
+- Live: `test/destructive-migrations.test.js` fails on a new unlisted `DROP COLUMN` (probed 2026-09-25);
+ `test/rollback-newer-writes.test.js` passes with the release of 2026-09-18 on a database migrated by
+ 2026-09-25's code.
@@ -31,3 +31,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -31,3 +31,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |
| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |
| [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |
| [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |
| [ADR-027](ADR-027-tools-platform-api.md) | Tools platform API: descriptors, one run API, caller tiers, probes, abuse log, legacy sunset | Accepted 2026-09-23; contracts v0.33.0, routes planned |
| [ADR-027](ADR-027-tools-platform-api.md) | Tools platform API: descriptors, one run API, caller tiers, probes, abuse log, legacy sunset | Accepted 2026-09-23; contracts v0.33.0, routes planned |
+| [ADR-028](ADR-028-expand-migrate-contract.md) | Expand, migrate, contract: schema changes that survive a rollback (7-day window); destructive-statement check and rollback-with-newer-writes test | Accepted 2026-09-25; enforced in Live |