Skip to content
Updated Sep 26, 2026 by Barča Dvořáková · Owner: analysisactiveentity Edit on GitHub

Entity: file ​

Entity Type: Database table

Description: The storage-registration record behind every upload in EM3 (readings-import today; document-center and others later). It carries metadata and a validated content lifecycle — the binary payload itself lives entirely in object storage, never in Postgres. rawObjectKey/cleanObjectKey are pointers into that storage (raw the as-uploaded object, clean the post-scan accepted copy); sizeBytes/rawSha256 describe those bytes, declared at request time and re-verified by streaming digest at registration. file has its own standalone lifecycle: POST /v1/files (request a presigned upload), POST /v1/files/:id/confirm (scan and register), GET /v1/files/:id (metadata), GET /v1/files/:id/download (stream), gated by tenant.files.read/tenant.files.write. Other entities only ever reference an existing file row via fileId; none of them create one.

Referenced by: document.fileId and dataImport.fileId (retargeted from document(id) to file(id) by migration 202608261002-data-import-file-fk.sql).

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idPrimary key of the entity.UUIDuuidv7() — DB defaultYesYesUUID v7-Primary Key018fa51f-fda1-79f4-8461-2cb8f1cabc10
tenantIdTenant this record belongs to. Bound from current_setting('app.tenant_id'), never accepted from the caller.UUIDcurrent_setting('app.tenant_id')::uuid — DB defaultYesNoUUID v7Foreign Key → tenant; must exist-018fa51f-fda3-7c63-b5a9-3fa33dc989de
rawObjectKeyObject-storage key of the as-uploaded, pre-scan object. Internal — deliberately excluded from client-facing metadata responses.String-YesNo-Non-empty-tenants/abc/raw/018fa51f.bin
cleanObjectKeyObject-storage key of the post-scan, accepted copy, written once registration completes. Internal — deliberately excluded from client-facing metadata responses.String-YesNo-Non-empty-tenants/abc/clean/018fa51f.bin
originalFileNameThe file name as supplied by the uploading client.String-YesNo-Non-empty-invoice_march.pdf
sizeBytesSize of the file in bytes, in its verified (post-registration) form once known.BigInt-YesNo-Positive integer-245678
rawSha256SHA-256 digest of the raw uploaded bytes, verified by streaming digest at registration.String (char(64))-YesNo64-character hexExactly 64 charactersname: idx_file_raw_sha256, type: btree3a7bd3e2360a1f...
mimeTypeDetected MIME type, set once registration completes.String, nullablenullNoNo---application/pdf
registrationStatusContent-validation lifecycle state.String (enum)quarantinedYesNoenum: quarantined, clean, blocked, copy_failedMust be one of the listed valuesname: idx_file_status_created (registration_status, created_at), type: btreeclean
failureReasonHuman-readable reason a file was blocked or failed the copy step.String, nullablenullNoNo---Virus signature detected
createdAtTimestamp of entity creation. Immutable.Timestamp with time zonenow()YesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ--2026-03-01T09:00:00Z
updatedAtTimestamp of last update.Timestamp with time zonenow()YesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ--2026-03-01T09:05:00Z
createdByActor who created this record.String-YesNotype:actor--user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2
updatedByActor who last updated this record.String-YesNotype:actor--user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2
deletedAtSchema-present but unused — every current delete path (SweepStaleQuarantinedFilesUseCase) hard-deletes the row instead of soft-deleting it.Timestamp with time zone, nullablenullNoNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZNever set by any current write pathname: idx_file_deleted_at, type: btree (partial, WHERE deleted_at IS NULL)null

Audited fields ​

Recorded on created (in full), updated (changed only) and deleted (in full): originalFileName, sizeBytes, rawSha256, mimeType, registrationStatus, failureReason.

Excluded:

  • rawObjectKey, cleanObjectKey — direct pointers to the binary payload's storage location, not business metadata; carrying either by value would put a storage-location reference into a trail readable by anyone holding audit:read tenant-wide, a broader grant than tenant.files.read. Both still appear by name in the changed-fields list of any entry where they are set or change — a column excluded by value is still named.

file self-subjects (subjectEntityType = file, subjectEntityId = file.id) — it has a standalone lifecycle and no owning parent. Registered for entityName resolution — resolves to originalFileName (architecture 61-audit-log.md §7.4 in the code repo).