ECSPersistence migration transaction repair
Made each migration and its version row atomic; prevents half-applied schemas on retry.
Scope: rdmsm4x only, ISSUE-20260926-29, codex/ecsmigtx, production library. Code preserved in commit 749ea473405c507a150313a914c7acd47e32a6f6 on fix/migrator-txn-0926; original source retained at 5a18d7db8f07fcb5e9d67aee6ebaddda02ebc079. No shared main or consumer source changes, no deployments or ticket resolution.
ISSUE-20260926-29 โ completed implementation and validation
Commit: 749ea473405c507a150313a914c7acd47e32a6f6 on
fix/migrator-txn-0926, based on
5a18d7db8f07fcb5e9d67aee6ebaddda02ebc079. Owner:
codex@rdmsm4x/ecsmigtx. Project: ECSPersistence, production. Worktree:
/Users/richh/dev/_worktrees/ecspersistence-migrator-txn-0926
(clean). No merge, push, deployment, or ticket resolution performed.
Change
Internal _withAsyncTransaction<T: Sendable> begins
an immediate transaction or a nested savepoint, maintains
transactionDepth with defer, commits/releases on success,
and rolls back/rethrows on error. Each migration body and its version
INSERT now run inside one transaction. Public migration API and
ECSDatabaseMigrator alias are unchanged. Doc comments explain actor
reentrancy, exclusive-use requirement and prohibition on VACUUM /
journal_mode / foreign_keys changes inside a step. No user_version
behavior changed.
Tests
nice -n 10 timeout 1800 swift test: 40 XCTest
tests, zero failures (37 existing + 3 added). The separate
Swift Testing runner has zero tests. Full output:
swift-test.log. Before the fix, the three new regressions
produced four failures/errors across two failing tests
(regression-before.log); nested transaction test already
passed. After the fix the suite proves:
- Step 2 CREATE TABLE then throw preserves step 1, rolls back step 2, skips step 3, retains only version 1 and user_version 42; a fixed second run applies 2 and 3.
- Synchronous withTransaction works inside a migration, including a caught nested rollback followed by more work and a successful outer commit.
- A trigger rejects the version-2 INSERT after the migration DDL; that DDL rolls back, version 1 remains, and removing the trigger permits a clean retry.
git diff --check passes. Source-compatible consumers
compile against this branch.
Consumer results
All use Package.swift
.package(path: "../../lib/ECSPersistence"), overridden only
in a temporary detached-main app worktree to the assigned ECSPersistence
worktree. Command:
nice -n 10 timeout 1800 swift build --package-path <temporary-app-worktree> --scratch-path <handoff>/build-<app>-branch -j 2.
| App | Main source SHA | Branch build |
|---|---|---|
| Voight | b448dc35eddf5ae34140427a133640ab4be43e5a |
PASS, exit 0 |
| xsite | 04b949064747a36a142e508cfde4c2e95a537e41 |
PASS, exit 0 |
| iCloudMonitor | 8c01e28f759aa5da189d5ac12128d2955d364410 |
PASS, exit 0 |
| replicantDB | f07649866fc7be23e11df68132d81761b8253a1a |
PASS, exit 0 |
Build durations reported by Swift: Voight 29.95s; xsite 20.69s;
iCloudMonitor 19.55s; replicantDB 58.52s. All are host arm64 debug
compatibility builds, not release builds. No main comparison was needed
because every branch build passed. Each
<app>-dependency-proof.txt records compiler
output-map evidence that all nine library source inputs came from the
assigned worktree. <app>-override.patch preserves the
exact temporary override; dependency-revisions.txt records
other library revisions/status.
All four temporary ecsmigtx-<app>-0926 worktrees
were removed with git worktree remove after restoring only
our Package.swift override. Canonical app status is unchanged
(before/after files match); no consumer changes committed. Detailed
reproduction: REPRODUCE.md. No files under arista or
apps/autoCE were touched.
Scope and limitations
Changed in commit: AGENTS.md (production mode), SESSION-STATE.md, Sources/ECSPersistence/ECS0Persistence.swift, Sources/ECSPersistence/ECS0DatabaseMigrator.swift, Tests/ECSPersistenceTests/ECSPersistenceTests.swift.
Async actor reentrancy is explicitly documented, not prevented: callers must reserve this connection while running migrations. External side effects are not transactional. Library lead integration/review is pending; ISSUE-20260926-29 remains in progress. Apple Notes publication is PENDING because launchctl managername is Background; the durable host changelog file is retained. No physical owner action is required for this code handoff; Notes can be published by the existing GUI-session publisher.
Additional records touched: /Users/richh/dev/PROJECTS.md (additive milestone), ~/.agent-coordination/checkins/codex-ecsmigtx.json, internal agent messages, ISSUE-20260926-29 comments, this changelog and the named handoff directory. Commands and raw logs: REPRODUCE.md and logs in the handoff directory. Verification: full suite 40/40, all four consumer builds exit 0, worktree cleanup and unchanged canonical consumer status verified; git diff --check clean. Backup/undo: source base remains in Git; integration has not happened. To undo the code commit later, use an ordinary git revert of 749ea473405c507a150313a914c7acd47e32a6f6 in the owning isolated branch. No user data migration or deletion occurred. Outstanding owner actions: none for implementation. Library lead retains integration; Notes publication remains pending for a GUI-session publisher.