Appearance
GrydFiles Module
GrydFiles is the platform's record of a file: who uploaded it, the hash the server computed, what the antivirus said, who still points at it and when it may die. The bytes travel between the client and the bucket by presigned URL and never through the API.
The decisions behind it are in ADR 0010; fields, routes, error codes and invariants are in the specification. These pages are about using and running the module.
The gap it closes
The framework could already talk to S3, Azure Blob Storage and Google Cloud Storage and issue presigned URLs. What no module had was the record around the bytes. GrydReports kept the output of a report as a URI in a string; GrydNotifications kept each e-mail attachment as byte[] in a column. Neither had a hash, an antivirus verdict, a retention rule or an answer to "does anybody still need this?" — and without that answer nothing can ever be purged safely. Both now store their files here.
✨ Features
| Feature | Description |
|---|---|
| 🚚 Two-phase upload | Reserve (POST /files), PUT straight to the bucket, confirm (POST /files/{id}/complete) |
| 🦠 Quarantine before reference | Pending → Scanning → Available; nothing is referenceable without a verdict, and there is no manual release |
| 🔑 Server-side identity | sha256 computed on the one read that also feeds the type detector and the engine |
| 🧪 Content-type check | By content signature, OOXML package or text validation — never by extension; unconditional, even with the antivirus waived |
| ♻️ Dedupe inside a tenant | One blob per content per tenant; never across tenants |
| 🔗 Reference registry | Consumers register what they hold (ownerScope + ownerKey); the count is what purges read |
| ⏳ Declared retention | UntilReleased, RetainUntil or Permanent, from the upload or the profile; dates only move forward; legal hold |
| 🪦 Purge with a tombstone | The object dies, the record keeps hash, size, type, profile, scan result and location |
| 📥 Short download URLs | attachment + application/octet-stream, a few minutes of validity, a line in the audit trail per URL |
| 🩺 Scanner health | GET /files/scanner-status, a health check and two alerts: queue and signature age |
| 🏢 Multi-tenant | Tenant at the root of every key; another tenant's file is answered exactly as a missing one |
| 🧩 In-process consumers | Modules in the same host use IFileService or the server-side store, with no HTTP |
📦 Packages
📦 GrydFiles
├── GrydFiles.Core # StoredFile, FileReference, FileScanAttempt, error codes, options
├── GrydFiles.Application # IFileService, IServerFileStore, profiles, scan contract (IFileScanner)
├── GrydFiles.Infrastructure # EF Core (PostgreSQL), the scan job and the five recurring jobs
├── GrydFiles.API # FilesController — the ten routes
├── GrydFiles.Infrastructure.ClamAv # The clamd adapter and the generated clamd.conf
└── GrydFiles (meta-package) # Core + Application + Infrastructure + API| Depends on | Why |
|---|---|
Gryd.Infrastructure object storage (AddGrydObjectStorage) | The bucket, the provider and the presigned URLs |
| GrydJobs | The scan queue and the recurring jobs |
An IAuditStore | Download URLs, quarantines, purges and legal-hold releases are written to the trail |
| PostgreSQL | The module's own schema; the partial unique indexes of the dedupe are PostgreSQL's |
🔄 The life of a file
Infected is terminal: no permission, admin:system included, brings a file back from it. Failed never releases anything either; it only waits for another pass.
🚀 Where to go next
| Page | What it covers |
|---|---|
| Getting Started | Registration, migrations and the minimal flow |
| Configuration | The GrydFiles section, profiles and the scanner limits |
| API Reference | The ten routes, the error contract and the permissions |
| Antivirus & Quarantine | Running clamd, the retry budget and the two alerts |
| Download | URL validity, the two headers and the trail |
| Retention & Purge | How a file dies, and the consistency sweep |
| Specification | v1.2, with the errata of the implementation |