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

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 fieldAccepted source column / key (first non-empty wins)Required?
external idexternal_id, source_id, id, key, req_id, requirement_idYes — this is the idempotency key
titletitle, name, summary, requirementYes
descriptiondescription, desc, detail, detailsNo
body textbody, body_text, text, content, acceptance_criteriaNo
prioritypriority, prio, rank (integer)No (defaults to 0)
statusstatus, aipm_status, backlog_statusNo (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)

  1. 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
  2. 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.
  3. Run the import. Point it at your target project and file:
    python -m aipm.flatfile_import \
        --project proj_xxxxxxxxxxxxxxxx \
        --file reqs.csv \
        --source-system flatfile
    The importer prints a JSON summary: how many rows were created, unchanged, updated, or skipped_existing.
  4. 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.
  5. 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
  6. 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:

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.

  1. 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.
  2. Restore the pre-migration backup.
    python scripts/aipm_restore.py \
        --target "postgresql://USER:PASS@HOST:PORT/aipm_dev" \
        backups/aipm_aipm_dev_20260615T120000Z.dump
  3. 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.
  4. 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

TaskCommand
Back up a databasepython scripts/aipm_backup.py --dir backups
Import a flat filepython -m aipm.flatfile_import --project proj_… --file reqs.csv
Import a MAS exportpython -m aipm.flatfile_import --project proj_… --file mas_export.json --source-system mas-export
Sync from a live MASpython -m aipm.mas_sync import --aipm-project proj_…
Roll back (restore)python scripts/aipm_restore.py --target "…/aipm_dev" backup.dump
Verify the audit chainpython -m aipm.db verify-audit