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.
56 lines
2.0 KiB
PHP
56 lines
2.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MeshVault\Catalog;
|
|
|
|
/**
|
|
* Shared public boundary for catalog operations used by importers and consumers.
|
|
*/
|
|
interface CatalogServiceInterface
|
|
{
|
|
/**
|
|
* Compose on the shared connection, using savepoints inside caller transactions.
|
|
* Success never commits caller work; failure rolls back only this operation.
|
|
* Supported: creation, then metadata, then ownership/status; or one registration,
|
|
* then item/job progress. Acquire multiple file locks in globally ascending ID order.
|
|
* Never enter catalog mutation holding job/item locks, acquire root/folder or
|
|
* metadata locks after file locks, or follow registration with metadata mutation.
|
|
* Arbitrary registration loops/batches are not supported: this is not a lock scheduler.
|
|
* Metadata uses FOR NO KEY UPDATE, compatible with ownership's implicit FK key-share.
|
|
*/
|
|
public function atomic(callable $operation): mixed;
|
|
|
|
public function modelMetadata(int $modelId): ?array;
|
|
|
|
public function updateModelMetadata(int $modelId, array $expected, array $replacement): array;
|
|
|
|
public function changeFileStatus(int $fileId, string $expectedStatus, string $targetStatus): array;
|
|
|
|
public function file(int $fileId): ?array;
|
|
|
|
public function fileByManagedPath(string $managedPath): ?array;
|
|
|
|
public function registerFile(
|
|
?int $modelId,
|
|
string $managedPath,
|
|
string $hash,
|
|
int $size,
|
|
?string $mime = null,
|
|
string $status = 'Present'
|
|
): int;
|
|
|
|
public function createModel(string $folderPath, string $modelName): int;
|
|
|
|
/**
|
|
* @return array{model: array, folder: array, files: array}|null
|
|
*/
|
|
public function aggregate(int $modelId): ?array;
|
|
|
|
public function assignFiles(int $modelId, array $fileIds): void;
|
|
|
|
public function reassignFiles(int $expectedModelId, int $targetModelId, array $fileIds): void;
|
|
|
|
public function detachFiles(int $expectedModelId, array $fileIds): void;
|
|
}
|