DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path standing-notes/loomworks-standing-note-migrations-and-the-code-that-runs-at-another-time-v0_1.md

Loomworks Standing Note — Migrations and the Code That Runs at Another Time — v0_1

Version. 0.1 Date. 2026-08-20 Status. Standing note for the Loomworks project. Read before writing any migration that constrains a value another process writes, or that seeds data a module also declares. Origin. Two incidents, generalised into one rule. CR-2026-226 (W7) post-merge fix, engine b606309; CR-2026-227 (W1) items 1-2, engine 720aa06.


1. The rule

A migration is code that runs at a different time from the code it agrees with. Every such agreement must be held by an assertion, never by hope.

Two shapes of the same problem, approached from opposite sides:

2. Incident one — the constraint that arrived too late (CR-2026-226, W7)

What happened. The system actor kind landed with migration 0112 widening the actor_kind CHECK. Correct for every deployed database. CI's staged stand-up then failed: fresh builds run scripts/bootstrap_engagements.py at revision 0033 (0034+ depend on its rows), and the bootstrap actor had started writing actor_kind='system' — a value the constraint would not admit for another 79 revisions. The 0039/0057 recreates write absolute sets, so they would have refused validation against existing system rows even had 0033 admitted them.

The fix. Migrations 0001/0039/0057 carry 'system' in their sets, so a fresh chain is wide from birth; 0112 stays the widening step for deployed databases. Same end state by both routes, noted in each edited file.

The rule it yields. Any new enum value written by bootstrap must be admitted by the constraint as of the revision bootstrap runs at, not merely by head. More generally: a migration chain has two readers — the deployed database walking forward, and the fresh database being stood up — and a change correct for one can be wrong for the other.

3. Incident two — the seed that could drift from its module (CR-2026-227, W1)

What happened, before it could happen. agent_identities is seeded, never minted: the runtime resolves roles and has no INSERT path, because a process minting its own governed identity record is authority-shaped (O10, DR-02). The roster is written as data — one reviewable structure rather than rows scattered through a migration body — on the same reasoning ruling R1 gave for SANCTIONED_SYSTEM_WRITERS: a roster written as data can one day become a governed object.

The tension that creates. Data in a module is live and refactorable. Data in a migration must stay valid against the code of its own era. A migration that imports the module breaks the chain the first time the module moves; a migration that copies it silently drifts.

The resolution. Both, plus a binding. The roster lives in agents/native_agent_roster.py as the live structure; migration 0113 carries the same roster as a single data literal in its own file and imports nothing; and test_native_agent_roster_matches_migration_seed parses the migration's AST and asserts the two agree exactly.

The rule it yields — generalise it to anything a migration seeds:

> Data literal in the migration. Live structure in the module. One test binding them.

Adding an entry is therefore three deliberate acts — module, migration, passing binding test. That friction is not incidental: it is human-only widening (O10) expressed as something a person actually feels, rather than as a rule nobody reads.

4. Why these two read together

They are the same coupling seen from either end. In both, a migration must agree with code that does not run when the migration runs — a bootstrap script running before it in the chain, or a module living after it in the tree. In both, the agreement is invisible at the point of edit: nothing in 0112 mentioned the bootstrap, and nothing in a roster module mentions a migration.

And in both, the failure is silent until it is expensive. The W7 constraint failed in CI rather than in review — one revision-boundary away from failing in a customer's fresh install instead. The roster drift would not fail at all: it would seed the wrong ids and be discovered when a citation stopped resolving.

5. The checklist

Before merging a migration, ask:

  1. Does any process write this column at a revision earlier than mine? Bootstrap scripts, data migrations, seed helpers. If yes, is the constraint true there?
  2. Do any earlier migrations recreate this constraint with an absolute set? If yes, they must carry the new value too, or a fresh chain re-narrows behind you.
  3. Does this migration seed data that also exists in code? If yes: literal here, structure there, test binding them. Never an import.
  4. Have I run the staged stand-up, not only upgrade head? The two paths are different readers and only one of them is the one you tested.

Related: loomworks-standing-note-observe-the-failure-before-trusting-the-check-v0_6; loomworks-standing-note-a-test-that-pins-a-literal-cannot-follow-a-ruling-v0_1.