Repository navigation
Let stacklets ship migrations the framework runs once, in order #220
Copy link
Copy link
Open
Description
Story
As a stacklet author, I want to ship a change to existing data as a migration that the framework runs once per instance, in order, records, and runs safely, so that moving files, renaming fields or rewriting records happens exactly once, the way database migrations work.
Context
Each data change today invents its own trigger and its own record of having run:
stack up memorymoves each topic's old to-do file to the lists (move_todos_to_listsinstacklets/memory/lib.py, called fromstacklets/memory/hooks/on_start_ready.py). It runs on every start and finds nothing to do after the first.- The archivist moves photos filed as bookmarks to notes (
stacklets/docs/bot/migration_photos_to_notes.py, fix(archivist): keep what the family writes about a capture #219). It runs a minute after the bot starts and leaves a marker file in its session directory.
None of them is ordered against another, none shows up in stack doctor, and a failure is a log line. Coming changes need the same: #217 (kind), #228 (tags), and the start of ADR-015 (a change to a record is a person's commit).
Proposal
- Files. A stacklet ships migrations as
stacklets/<id>/migrations/NNNN_<name>.py, each withdef run(ctx)and a docstring saying what it changes. Picked up by convention, like hooks, and given the same ctx. Work that needs a container goes through the existing seams:ctx.run_in_container, or a bot-runner command (the photo migration becomes one). - When.
stack up <id>runs the pending ones afteron_start_ready, only when the health checks passed, in number order. A command that starts several stacklets runs them in dependency order.stack updatenever runs them. A fresh install runs them all; they find nothing to do. - Safe. A migration is idempotent and either writes in one step or can resume. One that changes the family memory writes one Forgejo commit that checks each file's sha, so a concurrent writer makes it fail rather than overwrite; the bots keep running. Git history is the undo for the family memory. No down migrations, no dry run:
stack doctorlisting what is pending is the preview. - Record. The record lives with the data it describes, so restoring an older backup brings the old record back and the migration runs again:
- a migration of the family memory records itself in the commit that applied it, with a
Migration: <id>/<NNNN>trailer; - any other migration records itself in
<data_dir>/<id>/.migrations.json.
- a migration of the family memory records itself in the commit that applied it, with a
- Failure. The first failure stops the run, prints the error, and
stack upexits 1 with the containers left running. The rest wait for the nextstack up. - Visibility. With nothing pending,
stack upprints nothing extra.stack doctorlists pending and failed migrations, and data migrated by a newer release.stack updateto an older release names the migrations applied since and asks first. A commit that adds a migration carries anUpgrade:footer. - Lifetime. Migrations stay until 1.0. Then a minimum release to upgrade from is named, and older migrations are deleted.
- The two existing migrations move over and keep their behaviour.
Out of scope: down migrations, versioned data formats.
Definition of done
- A test: two migrations of one stacklet run once, in order, on
stack up; a secondstack upruns neither and prints nothing extra - A test: a failing migration stops the run,
stack upexits 1, and it runs again on the nextstack up; the one after it waits - A test: a family memory migration's record is the
Migration:trailer of its commit; with that commit gone from the history, it runs again - A test: a migration whose Forgejo commit meets a file changed underneath fails and changes nothing
- The to-do and photo migrations run through it, with their existing tests green
-
stack doctornames a pending or failed migration -
docs/stack-reference.mddescribes the migration contract
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
feat(stack): run stacklet migrations once, in order, on stack up
feat(memory): run the to-do list move once, as migration memory/0001
feat(docs): run the photo move once, as migration docs/0001
feat(stack): run stacklet migrations once, in order, on stack up
feat(memory): run the to-do list move once, as migration memory/0001
feat(docs): run the photo move once, as migration docs/0001
docs(adr): record a change to a record as a person's commit (#229)
feat(stack): run stacklet migrations once, in order, on stack up (#231)