# PostgreSQL migrations M2 issue #12 chooses the migration runner and ordered file naming. Keep schema changes versioned and repeatable; do not introduce a competing runner here. Subsequent issues #13-18 add physical folders, logical models, distinct files, metadata and import jobs. Application added_at timestamps are separate from file mtime. Migrations must never implicitly delete underlying library files. ## Catalog identity — 20261012120001 Back up the database and **stop all writers** (web requests, import workers and other database clients) before either upgrade or downgrade. Keep them stopped through preflight and DDL. Preflight is not a concurrency lock. Upgrade rejects duplicate managed paths and noncanonical paths, even without a collision, reporting offending file IDs. It makes file ownership nullable with RESTRICT on model deletion, replaces idx_files_model_path with unique idx_files_managed_path and nonunique idx_files_model_id, and makes idx_models_folder_name nonunique. Explicitly detach files before model deletion. Rows, IDs, timestamps, dependent metadata and unrelated indexes/FKs are preserved. Managed paths are lexical slash-separated relative names, preserved verbatim. Empty/dot/dot-dot segments, backslashes, NUL, absolute paths and URI/drive prefixes are rejected. Windows additionally rejects colons and trailing dots/spaces in segments. Case and Unicode are not normalized. This is not filesystem case comparison or symlink authorization; PathSafety still governs I/O. Migration validation performs no filesystem access. Investigate reported IDs and explicitly resolve catalog paths and ownership against the library before retrying. No automatic merging, renaming, reassignment or deletion repairs conflicts. Downgrade to 20261011120006 rejects unassigned file IDs and duplicate same-folder model titles before DDL. Explicitly assign files and disambiguate titles first. Only then are NOT NULL, per-model path uniqueness, unique folder/title and the old CASCADE model FK restored. Preflight failure leaves schema, data and migration version unchanged. Tasks 1–3 restore repository/importer compatibility and add the bounded catalog service: explicit same-title identities, ambiguity-safe legacy lookup, nullable registration and expected-owner associations using savepoint-backed transactions. Association changes update only model_id, not physical files or home folders. Task 4 verified the full suite on 2026-10-13: - Windows PHP 8.3.35: foundation passed; PHPUnit 377 tests / 2032 assertions / 28 skips; standard lint passed (Composer script commands invoked directly). - Fresh isolated PostgreSQL 17 / PHP 8.4.26 via `sh bin/test-postgres.sh`: Composer tests 377 / 2160 / 7 skips and lint passed. Log on the Docker host: `keith@192.168.2.23:/tmp/meshvault-catalog-tests-Ov96uNzU/pg-run-3.log`. - The suite runs production migrations in generated schemas, including populated down/up, dependent metadata, direct constraints, index preservation and unchanged preflight-rejection snapshots. This does not replace an operator backup/restore drill or deployment migration smoke acceptance. - First PG run exposed four SQLite-trigger syntax errors in rollback tests; dialect-specific test triggers now prove rollback after an actual earlier write. Six new two-session cases verify 55P03 contention, stale/no-op behavior, ascending execution parameters, nested rollback/lock release and root creation serialization. They passed existing locking code; no induced deadlock or mutation-red claim. No development database or historical workspace was touched. Independent full-slice review approved this bounded implementation; #28 is not complete. Importer confirmation/recovery, broader metadata/lifecycle contracts and unresolved M2/M3 acceptance remain open. See `docs/project-status.md` for the evidence and limitations.