# Catalog core foundation — proposed design Status: draft for user approval; no product implementation authorized by this document. ## Intent Build the missing #28 catalog contract before extending UI and importer workflows. The existing PHP/PDO repositories are useful foundations, but folder/name-based inference is not explicit grouping. A logical model must have stable identity, independent of its title and physical files. ## Goals - Explicit model creation and file assignment, including multipart STL models with individually selected supplementary images/documents. - Atomic catalog operations with rollback and foreign-key integrity on PostgreSQL. - Preserve file IDs, bytes, paths and timestamps when associations change. - Make future metadata, lifecycle, import and removal operations use one contract. ## Non-goals No browse UI, moves, physical deletion, downloads, duplicate decisions, retry engine or import crash-recovery fix in this first slice. Do not silently relabel unresolved M2/M3 acceptance as complete. No new framework or runtime dependency. ## Proposed domain rules (require approval) 1. A physical managed file has one durable catalog identity and belongs to at most one logical model. Unassigned files retain their file record. Shared ownership is outside MVP; separate models cannot silently adopt the same managed path. 2. Creating a model is explicit, not an `ensureModel(folder, name)` lookup. Equal titles do not imply equal identity. Empty draft models are allowed so creation and attachment can be separate user actions. 3. Assign/reassign/detach is a metadata operation: no copying, renaming or deletion. A model's files may occupy different physical folders; future move operations must preview the actual file set, not assume a single directory. The model retains a required home folder for organization; this does not relocate its files. 4. Future import preview defaults to one STL per model. Multipart grouping and supplementary associations must be explicit and confirmed; no automatic grouping based on folder names. This slice supplies the contract, not the preview UI. 5. Model removal will detach file records before deleting model-owned metadata. Files and uploaded-media bytes stay on disk. Physical deletion remains outside MVP. ## Architecture and builder choices Keep PHP, PDO, Phinx, Slim and existing repository conventions. Extend `CatalogRepositoryInterface` / `PdoCatalogRepository` rather than introducing a parallel catalog. Add a catalog service boundary for validation and transactions. Its transaction helper must coexist with an outer importer transaction on the same PDO connection without accidentally committing or rolling it back. Proposed operations: create model; read aggregate; assign a selected set of existing file IDs; reassign selected files from an expected current owner; detach selected files. Validation occurs before writes; all changes in a multi-file command commit or roll back together. Missing targets, duplicate IDs and stale expected ownership produce explicit domain failures. Lock selected file rows in deterministic ID order on PostgreSQL and recheck ownership under lock. Concurrent reassignment must not silently overwrite another user's decision. SQLite remains the fast default test backend, not proof of PostgreSQL locking. Add new migrations, never rewrite applied migrations. Make file ownership nullable and enforce unique managed relative paths across file rows, not just per model. Migration must detect conflicting existing paths and stop with actionable guidance; never merge or delete existing records automatically. Existing rows and IDs survive. Removal service semantics detach first; avoid a model-delete cascade that erases unassigned-file identity. Decide the exact FK action in the implementation plan. Remove the current unique model `(folder_id, name)` constraint to permit independent same-title models. Adapt legacy `ensureModel()` so zero matches creates, one match reuses, and multiple matches fail explicitly; never choose an arbitrary model. Its existing ON CONFLICT clause must change with the constraint. Keep this inferred grouping entry point only for importer compatibility, documented as incomplete. Do not claim #20 complete until the importer actually uses a confirmed manifest. Builder choices: reuse `PdoRepository` savepoints for nested transactions; require expected ownership for every association command (explicit null means unassigned, not no precondition); preserve file timestamps on association changes. Use RESTRICT on model deletion and explicit detach. Validate canonical managed relative paths with existing path-safety conventions before writes. Migration preflight must also reject noncanonical path aliases rather than silently repair them. Downgrade must fail safely if unassigned files or duplicate same-folder titles prevent restoration of old constraints; no automatic reassignment, renaming or deletion. ## Acceptance tests - Aggregate with two STL parts, selected image and document, with stable identities. - Reassignment/detach changes associations only; files remain byte-for-byte intact. - Invalid target, missing file, stale expected owner or FK failure rolls back all changes; outer transactions remain owned by their caller. - Two PostgreSQL sessions contest the same file; one cannot silently overwrite the other's ownership decision. Test deterministic lock ordering and explicit conflict/deadlock failure; automatic retries are outside this slice. - Migration preserves IDs/data; duplicate managed paths fail without destructive repair. Test forward migration and rollback constraints explicitly. - Default SQLite suite and isolated PostgreSQL suite pass; existing importer tests remain green while its workflow limitations stay documented. ## Follow-on order After this contract, implement confirmed immutable import manifests (#18/#20) and publication recovery (#24/#25), then progress/retry (#26/#27). Catalog CRUD and CSRF/concurrent-edit handling (#29), database-only removal (#30), safe download (#33), and serialized folder/model moves (#31/#32) follow as separately designed slices. ## Approval boundary The ownership/unassigned-file/empty-model rules above are proposals, not existing user decisions. Approve or revise them before creating the implementation plan.