papa-hq reads 01 research -> 03 decision -> 02 design. The order is a scar, not a choice: 02-DESIGN existed from its initial commit, and when adr/ was finally promoted on 2026-07-13 it took the next free number rather than its place in the sequence. By then design was too settled to renumber. hal-hq was three commits old, so it is not. adr/ becomes 02-DECISIONS and 02-DESIGN becomes 03-DESIGN, and following the folder numbers now walks the process in the order it happens: research produces a decision, the decision authorises a design. 00-GENESIS becomes 00-META, matching papa's rename from the same restructure. Every path reference rewritten across documents, frontmatter, playbooks and skills. All links resolve; all 58 frontmatter blocks parse and their path fields still point at files that exist.
69 lines
3.2 KiB
Markdown
69 lines
3.2 KiB
Markdown
---
|
|
status: accepted
|
|
date: 2026-05-14
|
|
deciders: jochen
|
|
reconstructed: true
|
|
---
|
|
|
|
# 6. Schema and state changes are numbered migrations, in the same language as the code
|
|
|
|
> Reconstructed after the fact from the evidence cited below.
|
|
|
|
## Context
|
|
|
|
Modules own persistent state. That state has to change as they change, across nodes that are
|
|
at different versions, some of which have data that predates the change.
|
|
|
|
Two things were being done that do not survive contact with a second node. Schema was created
|
|
at startup, so what a table looked like depended on which version last started. And migrations
|
|
were shell scripts, so they could not use the types, connection handling or helpers the module
|
|
already had, and were not compiled or checked with it.
|
|
|
|
## Considered options
|
|
|
|
1. **Startup SQL / create-if-missing.** Rejected. It converges only for a node that started
|
|
with the newest version. A node that never restarts never migrates; a node that restarts on
|
|
an old version can undo a change.
|
|
2. **Shell migrations.** Rejected. Unchecked, untyped, and a separate dialect from the module
|
|
they belong to. Also, as later discovered, packaged differently — and therefore
|
|
occasionally not packaged at all.
|
|
3. **Numbered migrations in the module's own language, compiled with it.** Chosen.
|
|
|
|
## Decision
|
|
|
|
Every schema or state change is a numbered migration file, written in the same language as the
|
|
module and compiled with it. There is no startup schema creation and no ad-hoc statement.
|
|
|
|
Rules that come with it:
|
|
|
|
- The initial migration is **frozen** once it has run anywhere. It is never modified; a change
|
|
is a new number.
|
|
- Every statement is **idempotent** — guarded so that re-running is safe.
|
|
- A change needs **both** a baseline for a fresh installation and an incremental migration for
|
|
installations that already exist. Code referencing a column requires that the migration
|
|
creating it exists.
|
|
- Migration numbers are unique. A duplicate prefix is a defect, not a style issue.
|
|
|
|
## Consequences
|
|
|
|
- A node at any version converges to the current schema by running the migrations it has not
|
|
run.
|
|
- Migrations are checked by the same compiler as the code, and a migration that does not
|
|
compile fails the build rather than the deployment.
|
|
- The rules are enforced unevenly. Duplicate prefixes have shipped repeatedly and been fixed by
|
|
renumbering afterwards; the mesh now validates for them, which is the check this rule needed
|
|
in order to be real.
|
|
- A migration directory is a feature like any other, which means it is packaged like any other
|
|
— and when packaging is wrong, migrations silently do not ship. This has happened.
|
|
- Freezing the initial migration means a fresh installation replays the entire history. That
|
|
cost grows and nothing currently bounds it.
|
|
|
|
## References
|
|
|
|
- `fix(agents): convert workflow migrations to TypeScript` (#50), 2026-05-14.
|
|
- `feat(dev_validate): guard against duplicate migration numeric prefixes` (#379), 2026-06-26 —
|
|
the rule acquiring a check.
|
|
- Knowledge base: `migrations/schema-drift`, `noxflow/troubleshooting/migration-number-collision`.
|
|
- Packaging failures: `troubleshooting/shell-migrations-never-packaged`,
|
|
`troubleshooting/provision-migration-never-applied`.
|