# Catalog and Governed Media

Phase 8 introduces relational service and template catalogs, governed taxonomy,
and an integrity-tracked media library. These modules provide administration
contracts only; public catalog delivery and downloadable media endpoints are
deliberately deferred until later publishing/SEO phases.

## Catalog lifecycle

Services, templates, categories, industries, features, and technologies share
stable public UUIDs, unique slugs, ordering, descriptive copy, an optimistic
`lock_version`, and this lifecycle:

```text
draft -> published -> archived -> draft
```

A published record cannot return directly to draft. Restore an archived record
to draft, review it, and publish again. Only draft, unreferenced records may be
hard-deleted. Published records are archived so historical links and audit
evidence remain interpretable.

Published services require a published category. Any relationship selected on
a published service or template must also point to a published catalog record.
Archived records cannot be newly attached. A category, industry, feature, or
technology cannot be archived while a published service or template depends on
it. These checks and relationship synchronization occur inside transactions.

## Relational composition

Services may relate to one category and many industries, features,
technologies, templates, and media assets. Templates may relate to many
services, features, technologies, and media assets. Composite primary keys on
all catalog pivots prevent duplicate relationships and support reverse lookups.

The application uses stable morph aliases (`service` and `template`) for media
attachments rather than storing PHP class names. Attachment rows carry a role
and display order. Deleting an attachment never deletes the shared media object.

## Governed media

The upload boundary accepts JPEG, PNG, WebP, and PDF up to
`CATALOG_MEDIA_MAX_KB`. It validates both detected MIME type and original
extension as a permitted pair. SVG and executable formats are not accepted.

Accepted files are stored on `CATALOG_MEDIA_DISK` under a date-partitioned,
random UUID path. The database records the original display name, detected MIME
type, normalized extension, byte size, SHA-256 checksum, visibility, image
dimensions when available, uploader, processing state, and alternative text.
Storage paths are not included in audit events.

Phase 8 does not expose a file-serving route. A later public-delivery adapter
must enforce visibility, safe response headers, authorization for private
objects, and malware-scanning policy before serving content. `public` is
therefore catalog intent, not permission to bypass the storage boundary.

Media metadata uses optimistic locking. Physical deletion is permitted only
when no service or template attachment exists. The metadata row is deleted in a
transaction and the exact object path is removed afterward. Failed database
creation removes the just-written object to avoid orphaned uploads.

## Authorization and audit

Permissions stay deliberately granular:

- `services.view`, `services.create`, `services.update`, `services.delete`
- `templates.manage`, `categories.manage`, `industries.manage`
- `features.manage`, `technologies.manage`, `media.manage`

Policies protect every controller and Form Request. Navigation and dashboard
cards use the same policy/permission decisions, so unavailable areas are absent
from rendered HTML while server-side authorization remains authoritative.

Audit records cover catalog creation, ordinary updates, publication, archival,
deletion, and media upload/update/deletion. Catalog events contain only the
record name, slug, and lifecycle state. Media events contain integrity and
classification metadata, never bytes, storage paths, or alternative-text copy.

## Operational checks

- Back up the configured media disk independently from the relational database
  and reconcile objects by `disk`/`path` and SHA-256 checksum.
- Keep `CATALOG_MEDIA_DISK` private unless a reviewed delivery adapter mediates
  every request. Do not expose `storage/app` as a web root.
- Alert on upload failures, checksum errors, orphan cleanup failures, rapid disk
  growth, or repeated optimistic-lock conflicts.
- Add malware scanning/quarantine before enabling untrusted PDF delivery or
  widening the MIME allow-list.
- MySQL 8 CI is authoritative for foreign-key restrictions, composite pivot
  keys, and production query plans.
