File Storage Reference Public

Research Evidence

The live-file evidence this ERPat File Storage guide is built on — the manifest, config sidecars, four migrations, controllers, service libraries, cron jobs, and passing module tests, each with what it confirms.

Guide version: r1 Module version: 1.0.0 Updated: 2026-07-30 Estimated time: 6 min 3 views

Why this page exists

This guide describes behavior, not aspiration. Every claim elsewhere in the documentation traces back to a source file in the shipped module. This page is the audit trail for maintainers and reviewers: it lists the evidence, states what each item proves, and flags the one place where a stated capability is a future integration rather than a shipped feature.

Grounding rule: if it is not in the tables below, it is not asserted in the guide. The scanner hook is the deliberate exception — see Scanner status.

Manifest — module.json

EvidenceWhat it confirms
Manifest slug: file_storageThe module key is file_storage; the enable setting is module_file_storage; controllers gate with with_module("file_storage") and the left menu appears under "Storage".
permissions[] listThe full permission surface: base file_storage (+ _create/_update/_delete), sharing (_share_internal, _share_external, _manage_permissions, _create_space, _manage_space, _create_file_request), and the high-risk governance set (_view_activity, _manage_quarantine, _manage_policies, _legal_hold, _purge).
api / feature declarationsThe module ships BOTH API surfaces — a client Integration API (/api/v1/file-storage/*) and an End-User API (/v1/api/file-storage/*) — plus cron jobs, widgets, and seeders.
No tenant_id anywhere in the schema declarationsMulti-tenancy is achieved by tenant-DB isolation, not a tenant column — consistent with ERPat's database-per-tenant model.

Config sidecars — config/*.php

EvidenceWhat it confirms
config/module_config.phpmodule_key equals the manifest slug (file_storage) and the module registers under the "Storage" category in Manage Modules.
config/permissions.phpThe permission keys are owned by the module (not hardcoded in core Roles.php) and appear in the Roles editor only while the module is enabled — including the governance keys as a distinct high-risk group.
config/menu.php + config/default_menu.phpThe single tabbed workspace lands under "Storage" in the live sidebar, and the default-menu slice mirrors it (names in lockstep) so it survives "Restore to default".
config/routes.phpAll module routes — the app workspace, the governance console, the public share pages, and both API surfaces — are declared in the module, never in core.
config/api_routes.php + config/euapi_routes.phpThe client API mounts at api/v1/file-storage and the end-user API at v1/api/file-storage; both are globbed into the OpenAPI generators.
config/api_scope.phpTwo client scopes exist and are gated per endpoint: filestorage:read and filestorage:write.
config/widgets.php (+ widget helper)Two dashboard widgets are registered and gated on the module setting: "My Recent Files" and "Storage Usage".

Migrations — the four schema layers (no tenant_id)

The schema is delivered in four idempotent, reversible migrations that map one-to-one to the release plan. None of the tables carry a tenant_id column — isolation is by tenant database.

Migration (evidence)TablesWhat it confirms
Foundation (R1) file_spaces, file_nodes, file_versions, general_file_references, file_acl_entries, file_upload_sessions, file_activity_events, file_favorites Folders and logical files (file_nodes), immutable versions pointing back to general_files (file_versions), the reference ledger that makes purge reference-aware (general_file_references), the role-based ACL (file_acl_entries), validated-intake sessions (file_upload_sessions), the activity feed (file_activity_events), and starring (file_favorites).
Collaboration (R2) file_comments Comments on files, plus the space-membership and Shared-with-Me grant plumbing that the collaboration release adds on top of the foundation tables.
Sharing (R3) file_share_links External share links store only the SHA-256 token hash (never the raw token), the scope, expiry, max-opens counter, Argon2id password hash, OTP settings, and the download-allowed flag — the evidence that raw secrets are not persisted.
Governance (R4) file_legal_holds, file_legal_hold_items Legal holds and their held-item links exist as first-class tables, enabling fail-closed blocking of permanent purge.
Every table ends with the standard audit block and a soft-delete flag; migrations guard schema changes (SHOW COLUMNS/SHOW INDEX) and ship a reversible down(), so they re-run safely on already-provisioned tenant databases.

Controllers — controllers/*.php

Controller (evidence)Base & gatesWhat it confirms
FileStorageApp_Controller; with_module + with_permissionThe tabbed workspace (My Files, Shared with Me, Recent, Starred, Trash, Activity) as one lazy-tab page, plus spaces() — the dedicated Team Spaces page (space rail + the active space’s directory, deep-linkable to a space and folder).
FileStorage_adminApp_Controller; governance permissionsThe separate admin console with Policies, Quarantine, Legal Holds, and Risk tabs — distinct from the everyday workspace.
FileShareGuestControllerPublic, unauthenticated share landing: token resolution, password/OTP gates, download, and upload-only file requests — accessible without a session.
FileStorage_apiApiAuthController; authorizeScopeThe client Integration API gates each action on filestorage:read / filestorage:write.
FileStorage_euapiEndUserApiAuthController; requireModule + requirePermit + selfUserId()The end-user API is strictly self-scoped — identity comes only from selfUserId(), never a caller-supplied id.

Service libraries — libraries/*.php

Business logic lives in eight single-purpose services, not in the controllers — the evidence that the module keeps SQL and rules out of the request handlers.

Service (evidence)What it confirms
File_registry_serviceFiles are recorded against ERPat's shared general_files registry as owner-less rows and reconciled from it — the mechanism behind isolation-by-construction.
File_node_serviceFolder/file node CRUD: create, rename, move, star, trash/restore within the logical tree.
File_upload_serviceValidated intake reusing ERPat's existing extension/MIME/magic-byte checks and local/S3 pipeline; no separate uploader.
File_lifecycle_serviceVersioning, soft-delete-first trashing, restore, and reference-aware safe purge.
File_authorization_serviceThe server-side ACL: roles (viewer/commenter/contributor/editor/manager/owner/upload_only) with folder + space inheritance, computed server-side.
File_share_serviceExternal link issuance — token hashing, scope, expiry, atomic max-opens, Argon2id password, and email OTP.
File_scanner_serviceA pluggable scanner HOOK exists (see the scanner note); it is the seam where real AV/CDR would attach.
File_purpose_policyThe purpose-policy registry that Governance → Policies edits.

Cron jobs — jobs/*.php

Six Advanced Cron jobs run per-tenant and each self-gates on module_file_storage — verified by their declared slugs and schedules.

Slug (evidence)ScheduleWhat it confirms
file_storage_upload_pipelineevery 1 minUploaded bytes are processed asynchronously through the intake pipeline.
file_storage_access_expirerevery 5 minExpired share links and grants are deactivated on a schedule.
file_storage_membership_syncdailyTeam Space membership is kept in sync with department membership.
file_storage_registry_reconcilehourlyThe reference ledger is reconciled against the shared general_files registry.
file_storage_safe_purgedailyPermanent purge runs only after the reference-aware, fail-closed checks pass.
file_storage_risk_scandailyThe governance risk dashboard is refreshed on a schedule.

Module tests — php erpat module:test FileStorage

EvidenceWhat it confirms
The module's tests/ suite passesThe manifest, config sidecars, route targets, permission ownership, and service contracts described above are structurally valid — the suite runs green under php erpat module:test FileStorage (not the core test:run suite).
Authorization / safe-purge assertionsThe ACL computes as documented and purge is blocked while a reference, current version, retention window, or legal hold is active — the fail-closed guarantees are exercised, not just asserted in prose.
Run it yourself: php erpat module:test FileStorage — this is the authoritative check that the module still matches this documentation.

Scanner status — the one deliberate gap

Upload validation today reuses ERPat's existing extension, MIME, and magic-byte checks. File_scanner_service is a pluggable hook only — a real anti-virus / content-disarm engine is a FUTURE integration and is not enabled in the shipped module. Do not document AV scanning as a live feature.
Was this guide helpful?

Report a content problem