Appearance
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 Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key of the entity. | UUID | uuidv7() — DB default | Yes | Yes | UUID v7 | - | Primary Key | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| tenantId | Tenant this record belongs to. Bound from current_setting('app.tenant_id'), never accepted from the caller. | UUID | current_setting('app.tenant_id')::uuid — DB default | Yes | No | UUID v7 | Foreign Key → tenant; must exist | - | 018fa51f-fda3-7c63-b5a9-3fa33dc989de |
| rawObjectKey | Object-storage key of the as-uploaded, pre-scan object. Internal — deliberately excluded from client-facing metadata responses. | String | - | Yes | No | - | Non-empty | - | tenants/abc/raw/018fa51f.bin |
| cleanObjectKey | Object-storage key of the post-scan, accepted copy, written once registration completes. Internal — deliberately excluded from client-facing metadata responses. | String | - | Yes | No | - | Non-empty | - | tenants/abc/clean/018fa51f.bin |
| originalFileName | The file name as supplied by the uploading client. | String | - | Yes | No | - | Non-empty | - | invoice_march.pdf |
| sizeBytes | Size of the file in bytes, in its verified (post-registration) form once known. | BigInt | - | Yes | No | - | Positive integer | - | 245678 |
| rawSha256 | SHA-256 digest of the raw uploaded bytes, verified by streaming digest at registration. | String (char(64)) | - | Yes | No | 64-character hex | Exactly 64 characters | name: idx_file_raw_sha256, type: btree | 3a7bd3e2360a1f... |
| mimeType | Detected MIME type, set once registration completes. | String, nullable | null | No | No | - | - | - | application/pdf |
| registrationStatus | Content-validation lifecycle state. | String (enum) | quarantined | Yes | No | enum: quarantined, clean, blocked, copy_failed | Must be one of the listed values | name: idx_file_status_created (registration_status, created_at), type: btree | clean |
| failureReason | Human-readable reason a file was blocked or failed the copy step. | String, nullable | null | No | No | - | - | - | Virus signature detected |
| createdAt | Timestamp of entity creation. Immutable. | Timestamp with time zone | now() | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-03-01T09:00:00Z |
| updatedAt | Timestamp of last update. | Timestamp with time zone | now() | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | - | 2026-03-01T09:05:00Z |
| createdBy | Actor who created this record. | String | - | Yes | No | type:actor | - | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| updatedBy | Actor who last updated this record. | String | - | Yes | No | type:actor | - | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| deletedAt | Schema-present but unused — every current delete path (SweepStaleQuarantinedFilesUseCase) hard-deletes the row instead of soft-deleting it. | Timestamp with time zone, nullable | null | No | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Never set by any current write path | name: 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 holdingaudit:readtenant-wide, a broader grant thantenant.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).