Migration Playbook
How to migrate a project into AIPM from an existing flat-file or MemoryAndSoul (MAS) source, and how to roll back a migration that went wrong.
This playbook satisfies transition requirements REQ-TRN-001 (import path), REQ-TRN-002 (rollback), and is itself REQ-TRN-003 (this page).
Golden rule. Every migration step is recoverable. You always take a verified backup before you change anything, and the rollback is simply restoring that backup. Imports never bypass AIPM's validation or its tamper-evident audit chain — a botched import can be undone, and the audit log shows exactly what happened.
Prerequisites
- Python 3.10+ with the source tree on
PYTHONPATH(export PYTHONPATH=src), or the installedaipmpackage (pip install .). - PostgreSQL reachable via a DSN. Set
AIPM_DATABASE_URLto your target AIPM database, e.g.postgresql://USER:PASS@HOST:PORT/aipm_dev. - A target project already exists in AIPM. Create one first
with
python -m aipm.db create-project --name "My Project"and note the returnedproj_…id. - For backups/rollback: the PostgreSQL client binaries
pg_dump,pg_restore, andpsqlon yourPATH. - An export of your source data as a flat file — a CSV or a JSON file. If you are coming from a MAS-only project, export its requirements to a JSON file first (see Migrating from MAS).
Input formats
The importer reads two flat-file shapes. Field names are matched case-insensitively, and several common aliases are accepted, so most existing exports import without reshaping.
| AIPM field | Accepted source column / key (first non-empty wins) | Required? |
|---|---|---|
| external id | external_id, source_id, id, key, req_id, requirement_id | Yes — this is the idempotency key |
| title | title, name, summary, requirement | Yes |
| description | description, desc, detail, details | No |
| body text | body, body_text, text, content, acceptance_criteria | No |
| priority | priority, prio, rank (integer) | No (defaults to 0) |
| status | status, aipm_status, backlog_status | No (defaults to candidate) |
A status, if given, must be one of AIPM's backlog statuses:
candidate, selected, promoted,
deferred, rejected. Any other value is rejected before
anything is written.
CSV example
external_id,title,description,priority,status
REQ-001,Login with SSO,Users sign in via corporate SSO,5,candidate
REQ-002,Audit export,Export the audit log to JSONL,3,selected
JSON example
A top-level array, an {"requirements": [...]} / {"items":
[...]} envelope, or a single object are all accepted:
{
"requirements": [
{"id": "REQ-001", "name": "Login with SSO", "priority": 5},
{"id": "REQ-002", "name": "Audit export", "status": "selected"}
]
}
Migrating INTO AIPM (step by step)
- Back up the target database first. Even an import into an
empty project should start from a known-good, restorable snapshot:
export PYTHONPATH=src export AIPM_DATABASE_URL="postgresql://USER:PASS@HOST:PORT/aipm_dev" python scripts/aipm_backup.py --dir backups # writes backups/aipm_<db>_<UTC-stamp>.dump - Dry-check your file. Confirm the format and that every row has an external id and a title. Malformed rows fail loudly with a clear message rather than importing half a file.
- Run the import. Point it at your target project and file:
The importer prints a JSON summary: how many rows werepython -m aipm.flatfile_import \ --project proj_xxxxxxxxxxxxxxxx \ --file reqs.csv \ --source-system flatfilecreated,unchanged,updated, orskipped_existing. - Re-run safely if needed. The import is idempotent:
re-running the same file creates no duplicates — rows are
keyed on their stable external id. Unchanged rows are reported as
unchanged. - Updating changed rows. By default a row whose content
changed since a previous import is left untouched and reported as
skipped_existing(the importer never silently clobbers). To apply changes, re-run with--update:python -m aipm.flatfile_import --project proj_… --file reqs.csv --update - Verify the result. Confirm the new backlog items appear
and that the audit chain is intact:
python -m aipm.db verify-audit # expect ok=True
What the importer guarantees. Every imported row goes through AIPM's gated create path — the same validated, audited function the rest of the system uses. That means: invalid statuses are refused; every create and update appends a tamper-evident row to the SHA-256 audit hash chain; and a malformed file aborts the whole batch (all-or-nothing) rather than leaving a partial import behind.
Migrating from MAS (MemoryAndSoul)
There are two distinct MAS paths — pick the one that matches your situation:
- Live MAS server (the server is up and you want an ongoing
mirror): use
python -m aipm.mas_sync import --aipm-project proj_…. That pulls requirements from the running MAS over its MCP tools and mirrors them into AIPM, with conflict detection. See the MAS-sync documentation. - MAS-only / offline export (you have a JSON dump on disk,
not a live server): export the MAS requirements to a JSON file, then import
that file with the flat-file importer using a distinct provenance tag so the
rows are clearly marked as a one-time migration:
python -m aipm.flatfile_import \ --project proj_… \ --file mas_export.json \ --source-system mas-export
Using a different --source-system value (e.g.
mas-export vs. flatfile) keeps the two migration
lineages independent: re-importing one never disturbs rows imported from the
other.
Rolling back a migration
A rollback is restoring the backup you took before the migration. The restore is recoverable and integrity-verified: AIPM re-checks the tamper-evident audit chain after the restore and fails loudly if it does not verify.
- Create the rollback target (if restoring elsewhere). A restore overwrites its target. To roll back in place you restore over the same database; to validate a backup safely, restore into a fresh throwaway database first.
- Restore the pre-migration backup.
python scripts/aipm_restore.py \ --target "postgresql://USER:PASS@HOST:PORT/aipm_dev" \ backups/aipm_aipm_dev_20260615T120000Z.dump - The integrity check runs automatically. After loading the dump, the tool runs the audit-chain verification and only reports success if the chain verifies. A corrupt or tampered backup is rejected with a non-zero exit code — it is never silently accepted.
- Confirm the rolled-back state. The unwanted migration rows are gone and the database matches the backup snapshot.
Production safety guard. aipm_restore.py
refuses to restore over the production database (aipm_dev
by default) unless you pass the explicit --allow-prod flag.
Clobbering the live database must be a deliberate, opt-in act — there is no way
to do it by accident.
Reference
| Task | Command |
|---|---|
| Back up a database | python scripts/aipm_backup.py --dir backups |
| Import a flat file | python -m aipm.flatfile_import --project proj_… --file reqs.csv |
| Import a MAS export | python -m aipm.flatfile_import --project proj_… --file mas_export.json --source-system mas-export |
| Sync from a live MAS | python -m aipm.mas_sync import --aipm-project proj_… |
| Roll back (restore) | python scripts/aipm_restore.py --target "…/aipm_dev" backup.dump |
| Verify the audit chain | python -m aipm.db verify-audit |