Files
MeshVault/tests/Integration/PhysicalFileSnapshot.php
Cline a829d6e8c6 feat: catalog shared contracts (Tasks 1-4)
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.
2026-10-13 19:41:03 -05:00

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;
}
}