Problem Statement
Operators want to find and open fiscal documents (invoices and related files) in Filament the way they already browse CMS media, while website pictures stay publicly served. Today moox/media is built as a public CMS library: default public disk, MediaPicker, public API URLs. E-billing artifacts already live on private platform disks outside Media. write_protected can block edit/delete, but it is too coarse (it also blocks download in places), and there is no first-class “fiscal / private” mode. Moving fiscal bytes into Media without that mode risks leaking files next to CMS assets or treating GoBD storage like editable website media. Ownership of fiscal bytes (Media vs e-billing) is still a manager decision; the product still needs Media to be capable of private, searchable, non-mutable files.
Solution
Add a fiscal-capable / private media mode in moox/media that can store and list files on a private disk beside unchanged public CMS media. Harden protection so mutate/replace/delete stay blocked while inline show and download are independently configurable. Keep fiscal items out of the public media API by default. Document the open ownership choice (M1 / M2 / M3) for managers; do not migrate e-billing artifacts in this work. Parallel invoice UI (native relation managers, existing e-billing PDF preview) stays on the e-billing track per ADR 0018.
User Stories
- As a CMS editor, I want website images to keep using public media storage and MediaPicker, so that the site and current workflows do not change.
- As a platform admin, I want fiscal/document files to live on a private disk, so that they are not reachable via the public
/storage symlink.
- As a platform admin, I want to search and browse fiscal media in Filament, so that I can find files without leaving the admin UI.
- As a compliance-minded admin, I want protected fiscal media to refuse replace, image-edit, and delete, so that casual CMS edits cannot destroy records of record.
- As an AP clerk, I want to preview a protected PDF inline when policy allows show, so that I can check the document without downloading.
- As an AP clerk, I want to download a protected PDF when policy allows download, so that I can archive or forward it outside the browser.
- As a security admin, I want a configuration where show is allowed but download is denied, so that screen review is possible without easy exfiltration.
- As a security admin, I want a configuration where both show and download are denied for vault-like items, so that only metadata is visible in the library.
- As a security admin, I want fiscal/private media excluded from the public media API by default, so that website/API consumers cannot list invoice PDFs next to hero images.
- As a developer, I want an explicit opt-in if fiscal media must ever appear in a public API, so that exposure is never accidental.
- As a Filament admin, I want bulk delete to skip or refuse protected items, so that a multi-select cannot wipe fiscal files.
- As a Filament admin, I want form fields that would mutate protected media to be disabled, so that the UI matches policy.
- As a maintainer, I want
write_protected (or its successor) documented as Moox behaviour, not Spatie core, so that expectations of upstream Media Library stay accurate.
- As a maintainer, I want moox/audit able to record Media attribute/lifecycle changes, so that “who changed what, when” is visible like other Moox records.
- As a compliance officer, I understand that audit logs are not the same as download/access logs or file-byte hashes, so that GoBD controls are not assumed complete from audit alone.
- As a manager, I want a clear choice between Media owning bytes (M1), Media indexing only (M2), or e-billing owning search without Media (M3), so that ownership is deliberate before any migration.
- As a manager, I want the decision agenda to include retention years, delete authority, and backup/archive ownership, so that GoBD process is discussed with engineering options.
- As a manager, I want clarity whether KoSIT/veraPDF install trees and validation reports are in the same ownership decision as invoice XML/PDF, so that “all e-billing-related files” is not ambiguous.
- As a manager, I want portal customer downloads considered if Media owns or indexes artifacts, so that customer-scoped portal access is not confused with CMS MediaPicker access.
- As an e-billing operator, I want invoice PDF preview to keep working on the existing private e-billing route until ownership is decided, so that day-to-day review is not blocked on Media work.
- As an implementer, I want this ticket not to re-home e-billing storage paths, so that generation, validation, and delivery keep a stable source of truth for now.
- As a Moox package consumer, I want fiscal mode to stay generic (no host/customer brand naming), so that any installation can use private protected media.
- As a developer integrating MediaPicker, I want public CMS collections to remain the default for content models, so that fiscal collections are opt-in and hard to misuse on public pages.
- As a QA engineer, I want protected fiscal items to remain readable in admin when show/download allow it, so that “protected” does not mean “invisible.”
- As a platform admin, I want private-disk media URLs to require an authorized/temporary access path rather than a permanent public URL, so that private mode is enforceable.
- As a maintainer, I want README/CONTEXT to distinguish CMS media vs fiscal/private media and to point at the open M1/M2/M3 decision, so that future agents do not “just attach invoices to Media.”
- As an AP clerk, I want search by useful metadata (name, mime, collection, dates) for fiscal files, so that Filament search is actually usable for documents.
- As a security admin, I want fiscal items filterable into a dedicated collection or panel scope, so that they need not appear in the same undifferentiated grid as website assets unless we choose that.
- As a developer, I want Spatie per-item (or per-collection) private disk support used rather than forcing all media private, so that public and private can coexist.
- As a tax/compliance stakeholder, I want it recorded that a private Spatie disk alone does not equal GoBD completion, so that process (retention, integrity hash, access logging) stays explicit.
Implementation Decisions
- Package in scope:
moox/media only for this spec. E-billing artifact migration is out; host ADR 0018 governs interim invoice UI + storage boundary.
- Keep default CMS behaviour on the public media disk; introduce an explicit fiscal/private mode (naming TBD) for items or collections that must use a private filesystem disk.
- Extend protection beyond binary “everything locked”: default/always block mutate, replace, delete, image-edit; make show/preview and download separately configurable (item, collection, or policy — pick one primary configuration surface and document it).
- Fix today’s coarse behaviour where protected uploads are treated as non-downloadable even when fiscal browse needs download.
- Public media API: fiscal/private items excluded by default; opt-in only if ever required.
- Reuse and extend existing Moox
write_protected model/policy/UI guards rather than inventing a parallel flag without migration story; if a successor replaces it, migrate semantics clearly.
- Register Media with moox/audit where appropriate for attribute/lifecycle trails; do not claim access-log or hash coverage.
- No host- or customer-specific names in moox/media docs or code identifiers.
- Document open ownership options for managers:
- M1 — Media owns private bytes; consumers store media refs
- M2 — e-billing owns bytes; Media is index/link only
- M3 — e-billing owns bytes and search UI; Media stays CMS-only
- Engineering recommendation to record in the issue: prefer M3 or M2 unless Media commits to first-class fiscal mode; this ticket’s hardening is a prerequisite for a safe M1 later.
- Manager agenda must include: source of truth; CMS blast radius; one UI vs separate browser; retention years; delete authority; global vs dedicated search; API exclusion; backup under M1; order of harden-vs-decide; scope of KoSIT/veraPDF reports and source PDFs; portal download boundaries.
- Inline PDF viewing inside Media (iframe of authorized URL) may be added for
application/pdf when show is allowed; Spatie PDF→image conversion remains a thumbnail aid, not the document of record viewer.
- Do not change e-billing private preview routes or document storage columns in this package ticket.
Testing Decisions
- Prefer testing external behaviour at the protection/access policy seam: given a media item in fiscal/private mode with specific show/download/mutate settings, assert allowed vs denied outcomes for view/preview, download, update, delete, bulk delete, and public API visibility.
- Do not assert Spatie internal conversion implementation or exact on-disk folder strings unless the public contract documents them.
- Modules under test: media authorization/policy, Filament resource/actions behaviour for protected items, public media API list/show filtering, and configuration defaults (public CMS unchanged; fiscal private by default for API).
- Prior art in
moox/media tests is thin (mostly placeholders); follow Pest feature-test style used elsewhere in Moox packages (policy + HTTP/API + Filament where the repo already tests Livewire resources).
- Add at least: protected item cannot be deleted/updated; show/download matrix (allow/deny combinations); fiscal item absent from public API by default; public CMS item still creatable on public disk.
Out of Scope
- Re-homing e-billing XML/PDF/copy PDF, mail/manual sources, KoSIT/veraPDF installs or reports into Media
- Changing invoice ViewInvoice to Media URLs or implementing delivery-attempt / activity relation managers (e-billing / host parallel track)
- Declaring final M1/M2/M3 ownership (manager decision; this spec only prepares Media and documents the agenda)
- Implementing full GoBD archive/WORM, PAdES, or retention enforcement engines
- Download/access audit log product (may be noted as follow-up)
- Host- or customer-specific rules, naming, or fixtures inside moox/media
Further Notes
- Host ADR 0018 records the interim boundary: native Filament relation managers for invoice detail; e-billing-owned private PDF preview; fiscal Media ownership deferred; parallel tracks (harden media vs decide ownership).
Problem Statement
Operators want to find and open fiscal documents (invoices and related files) in Filament the way they already browse CMS media, while website pictures stay publicly served. Today
moox/mediais built as a public CMS library: default public disk, MediaPicker, public API URLs. E-billing artifacts already live on private platform disks outside Media.write_protectedcan block edit/delete, but it is too coarse (it also blocks download in places), and there is no first-class “fiscal / private” mode. Moving fiscal bytes into Media without that mode risks leaking files next to CMS assets or treating GoBD storage like editable website media. Ownership of fiscal bytes (Media vs e-billing) is still a manager decision; the product still needs Media to be capable of private, searchable, non-mutable files.Solution
Add a fiscal-capable / private media mode in
moox/mediathat can store and list files on a private disk beside unchanged public CMS media. Harden protection so mutate/replace/delete stay blocked while inline show and download are independently configurable. Keep fiscal items out of the public media API by default. Document the open ownership choice (M1 / M2 / M3) for managers; do not migrate e-billing artifacts in this work. Parallel invoice UI (native relation managers, existing e-billing PDF preview) stays on the e-billing track per ADR 0018.User Stories
/storagesymlink.write_protected(or its successor) documented as Moox behaviour, not Spatie core, so that expectations of upstream Media Library stay accurate.Implementation Decisions
moox/mediaonly for this spec. E-billing artifact migration is out; host ADR 0018 governs interim invoice UI + storage boundary.write_protectedmodel/policy/UI guards rather than inventing a parallel flag without migration story; if a successor replaces it, migrate semantics clearly.application/pdfwhen show is allowed; Spatie PDF→image conversion remains a thumbnail aid, not the document of record viewer.Testing Decisions
moox/mediatests is thin (mostly placeholders); follow Pest feature-test style used elsewhere in Moox packages (policy + HTTP/API + Filament where the repo already tests Livewire resources).Out of Scope
Further Notes