Task 1: Establish catalog identity and atomic file ownership - CatalogService, CatalogServiceInterface, ModelMetadata DTO - PdoCatalogRepository with atomic operations, savepoint support - FOR UPDATE file locks in ascending order, FOR NO KEY UPDATE for metadata - FK-compatible lock composition documented (spec §6) Task 2: M3 import & indexing engine delivery layer - ImportService, ImportWorker with durable collision handling - SpaceChecker, StreamingHasher, VerifiedCopier, StlValidator - FileEnumerator, LocalFilesystem, PathSafety - Console command wiring via ContainerWiringTest Task 3: Importer integration and repair - LegacyGuessedImportGroupingAdapter isolates first-directory guessing - ImportService uses adapter; ImportWorker uses CatalogServiceInterface - Null owners remain null; DomainException mapped to CATALOG_CONFLICT - SQL failure after registration/path update with caller sentinel rollback Task 4: PostgreSQL contention evidence and bounded acceptance - 6 new PostgresCatalogContractsTest cases with independent sessions - Generated schemas, 250ms lock timeouts, 5s statement timeouts - Ascending file locks, FOR SHARE contention, stale-owner rejection - Rollback/retry, nested transaction survival, root creation contention - Negative controls: replay FOR UPDATE removal (11 failures), metadata FOR UPDATE (4 FK KEY SHARE timeouts) - Fresh Docker PostgreSQL 17 / PHP 8.4.26: 473 tests, 2718 assertions, 10 skips, exit 0 All local: 474 tests, 2347 assertions, 54 skips, foundation/lint clean.
31 lines
899 B
PHP
31 lines
899 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MeshVault\Tests\Integration;
|
|
|
|
/** Sorted relative paths and physical contents/stat data; never catalog row data. */
|
|
final class PhysicalFileSnapshot
|
|
{
|
|
public static function capture(string $root): array
|
|
{
|
|
$snapshot = [];
|
|
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
|
|
$root,
|
|
\FilesystemIterator::SKIP_DOTS
|
|
));
|
|
foreach ($files as $file) {
|
|
$path = $file->getPathname();
|
|
clearstatcache(true, $path);
|
|
$relative = str_replace('\\', '/', substr($path, strlen($root) + 1));
|
|
$snapshot[$relative] = [
|
|
'bytes' => file_get_contents($path),
|
|
'size' => filesize($path),
|
|
'mtime' => filemtime($path),
|
|
];
|
|
}
|
|
ksort($snapshot);
|
|
return $snapshot;
|
|
}
|
|
}
|