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.
This commit is contained in:
1 parent
6b9fb54b4f
commit
a829d6e8c6
21 files changed
+1903
-61
No files matched your search
@@ -7,10 +7,11 @@ namespace MeshVault\Indexer;
|
||||
use MeshVault\Exception\PathSafetyException;
|
||||
use MeshVault\Indexer\Filesystem\PathSafety;
|
||||
use MeshVault\Indexer\SpaceChecker;
|
||||
use MeshVault\Repository\CatalogRepositoryInterface;
|
||||
use MeshVault\Catalog\CatalogServiceInterface;
|
||||
use MeshVault\Repository\ImportJobRepositoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use DomainException;
|
||||
|
||||
/**
|
||||
* Serializes every managed-library mutation behind one advisory lock file and
|
||||
@@ -32,7 +33,7 @@ final readonly class ImportWorker
|
||||
/** @param null|callable(): bool $shouldStop Consulted before each item. */
|
||||
public function __construct(
|
||||
private ImportJobRepositoryInterface $jobs,
|
||||
private CatalogRepositoryInterface $catalog,
|
||||
private CatalogServiceInterface $catalog,
|
||||
private StreamingHasher $hasher,
|
||||
private VerifiedCopier $copier,
|
||||
private StlValidator $validator,
|
||||
@@ -112,20 +113,21 @@ final readonly class ImportWorker
|
||||
$published = $this->publish($plan, $destination);
|
||||
$relative = $this->relative($published);
|
||||
// Catalog and progress commit together, so a crash here simply replays.
|
||||
$this->jobs->atomic(function () use ($item, $itemId, $plan, $published, $relative): void {
|
||||
// Update managed_path to the actual published path (with collision suffix if any)
|
||||
// so import_items.managed_path always points at the published file.
|
||||
$this->jobs->updateManagedPath($itemId, $relative);
|
||||
if ($item['model_id'] !== null) {
|
||||
$this->catalog->recordFile(
|
||||
(int) $item['model_id'],
|
||||
$this->catalog->atomic(function () use ($item, $itemId, $plan, $relative): void {
|
||||
// Catalog locks precede item/job locks on the shared PDO connection.
|
||||
try {
|
||||
$this->catalog->registerFile(
|
||||
$item['model_id'] === null ? null : (int) $item['model_id'],
|
||||
$relative,
|
||||
$plan['hash'],
|
||||
$plan['size'],
|
||||
null,
|
||||
'Present'
|
||||
);
|
||||
} catch (DomainException $error) {
|
||||
throw new ImportException('CATALOG_CONFLICT', $error->getMessage(), $error);
|
||||
}
|
||||
$this->jobs->updateManagedPath($itemId, $relative);
|
||||
$this->jobs->finishItem($itemId, 'Completed');
|
||||
});
|
||||
} catch (Throwable $error) {
|
||||
|
||||
Reference in new issue
Block a user