feat: establish catalog identity and atomic file ownership

This commit is contained in:
Cline committed 2026-10-12 19:38:38 -05:00
1 parent 390a7a557a
commit 6b9fb54b4f
16 files changed
+1381 -23

No files matched your search

+44 -3
View File
@@ -16,6 +16,45 @@ final class RepositoryDatabase
/** @var array<string, array{schema: string, connection: PDO}> */
private static array $schemas = [];
/** @var array<string, Config> */
private static array $configs = [];
public static function migrateTo(string $path, int $target): void
{
self::runTarget($path, $target, false);
}
public static function rollbackTo(string $path, int $target): void
{
self::runTarget($path, $target, true);
}
private static function runTarget(string $path, int $target, bool $down): void
{
$config = self::$configs[$path] ?? throw new \LogicException('Migrate this isolated path first.');
$manager = new Manager($config, new ArrayInput([]), new NullOutput());
$pdo = $config->getEnvironment('test')['connection'];
if ($pdo->inTransaction()) {
throw new \LogicException('Migration helpers require no active caller transaction.');
}
try {
if ($down) {
$manager->rollback('test', $target);
} else {
$manager->migrate('test', $target);
}
} catch (\Throwable $error) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $error;
} finally {
if (isset(self::$schemas[$path])) {
$pdo->exec('SET search_path TO "' . self::$schemas[$path]['schema'] . '"');
}
}
}
public static function postgresConfigured(): bool
{
$dsn = getenv('MESHVAULT_TEST_PG_DSN');
@@ -23,9 +62,9 @@ final class RepositoryDatabase
}
/** Never a hand-maintained test schema: drift must fail the suite. */
public static function migrate(string $path): PDO
public static function migrate(string $path, ?int $target = null): PDO
{
if (isset(self::$schemas[$path])) {
if (isset(self::$configs[$path]) || isset(self::$schemas[$path])) {
throw new \LogicException('Clean up the previous database before migrating this path again.');
}
$environment = ['adapter' => 'sqlite'];
@@ -48,7 +87,8 @@ final class RepositoryDatabase
'test' => $environment + ['connection' => $pdo],
],
]);
(new Manager($config, new ArrayInput([]), new NullOutput()))->migrate('test');
self::$configs[$path] = $config;
(new Manager($config, new ArrayInput([]), new NullOutput()))->migrate('test', $target);
if (isset(self::$schemas[$path])) {
// Phinx may add public to search_path; repository queries must remain isolated.
$pdo->exec('SET search_path TO "' . self::$schemas[$path]['schema'] . '"');
@@ -78,6 +118,7 @@ final class RepositoryDatabase
public static function cleanup(string $path): void
{
unset(self::$configs[$path]);
if (isset(self::$schemas[$path])) {
['schema' => $schema, 'connection' => $pdo] = self::$schemas[$path];
if ($pdo->inTransaction()) {