Portal Community
Prerequisites for All Scenarios These scenarios assume AddObjectStorage() is registered in Program.cs. Each example injects IObjectStorageProvider — the same interface regardless of whether you are running on MinIO, AWS S3, Cloudflare R2, or Backblaze B2. No scenario requires knowledge of the underlying backend.

Scenarios

  1. Invoice & Accounts Payable Archival
  2. HR Document Management
  3. AI Model Artefacts & Knowledge Base
  4. Workflow Attachment Pipeline
  5. Multimedia & Media Assets
  6. Audit Trail Evidence
  7. Data Export & Import Packages
  8. Multi-Tenant SaaS Isolation
  9. Storage Cost Estimation

Scenario 1 Invoice & Accounts Payable Archival

Business Context

Finance teams are legally required to retain every generated invoice as an immutable, auditable record for seven or more years depending on jurisdiction. These documents must be retrievable on demand — for audits, dispute resolution, or customer requests — and must not be alterable after the fact. Traditional file systems or database BLOBs cannot easily guarantee immutability; InfraHub Storage's CID-based design does.

The CID is a SHA-256 hash of the file content. Any modification to the file would produce a different CID, making silent tampering detectable. Storing the CID alongside the invoice record in the database creates a cryptographic link between the database row and the stored file.

Implementation

public class InvoiceService
{
    private readonly IObjectStorageProvider _storage;
    private readonly IInvoiceRepository _repo;

    public InvoiceService(IObjectStorageProvider storage, IInvoiceRepository repo)
    {
        _storage = storage;
        _repo = repo;
    }

    public async Task<Invoice> GenerateAndArchiveAsync(InvoiceRequest req, CancellationToken ct)
    {
        // 1. Generate the PDF bytes (your existing PDF generation logic)
        byte[] pdfBytes = await _pdfGenerator.RenderInvoicePdfAsync(req, ct);

        // 2. Store in InfraHub Storage — returns a CID (SHA-256 hex string)
        string cid = await _storage.StoreAsync(pdfBytes, "application/pdf", null, ct);

        // 3. Persist the CID alongside the invoice record in the database
        var invoice = new Invoice
        {
            InvoiceNumber  = req.InvoiceNumber,
            IssuedOn       = DateTime.UtcNow,
            TotalAmount    = req.TotalAmount,
            PdfCid         = cid          // link to immutable storage
        };
        await _repo.SaveAsync(invoice, ct);
        return invoice;
    }

    public async Task<Stream> RetrieveInvoicePdfAsync(string cid, CancellationToken ct)
    {
        // Retrieve the file at any point — even 7 years later
        return await _storage.GetStreamAsync(cid, ct);
    }
}
Zero-Cost Deduplication Re-generating the exact same invoice (identical bytes — same line items, same customer, same date) returns the same CID and stores zero additional bytes. If your system regenerates invoices from templates, this can reduce storage growth dramatically over time.

Scenario 2 HR Document Management

Business Context

HR departments handle a high volume of sensitive, personally identifiable documents: employment contracts, passport copies, tax forms, offer letters, and onboarding packs. These documents must be stored securely, accessible only to the appropriate tenant, and — in multi-entity businesses — never mixed across companies. They may also be subject to right-to-erasure requests under GDPR, requiring a clean way to reference and delete specific employee documents.

With PrependTenantID: true, InfraHub Storage automatically prefixes every key with tenant_{tenantID}/. This means HR documents uploaded for Company A can never be retrieved by Company B, even though they share the same bucket — the key namespacing enforces isolation at the storage layer, not just in application logic.

Implementation

public class HrDocumentService
{
    private readonly IObjectStorageProvider _storage;
    private readonly IEmployeeDocumentRepository _repo;

    public async Task<EmployeeDocument> UploadDocumentAsync(
        int employeeID,
        string documentType,   // e.g. "Contract", "Passport", "TaxForm"
        Stream fileStream,
        string contentType,
        CancellationToken ct)
    {
        byte[] bytes = await fileStream.ReadAllBytesAsync(ct);

        // TenantID is resolved automatically from IAiSessionContext —
        // the key stored in MinIO will be: tenant_42/a3f9b2c1...
        string cid = await _storage.StoreAsync(bytes, contentType, null, ct);

        var doc = new EmployeeDocument
        {
            EmployeeID   = employeeID,
            DocumentType = documentType,
            FileCid      = cid,
            UploadedOn   = DateTime.UtcNow,
            Version      = await _repo.GetNextVersionAsync(employeeID, documentType, ct)
        };
        await _repo.SaveAsync(doc, ct);
        return doc;
    }

    public async Task<IReadOnlyList<EmployeeDocument>> GetDocumentHistoryAsync(
        int employeeID, string documentType, CancellationToken ct)
    {
        // All versions — each upload creates a new CID, old CIDs are never overwritten
        return await _repo.GetAllVersionsAsync(employeeID, documentType, ct);
    }
}
Immutability by Design — Version Tracking in the Application Layer Uploading a revised contract creates a new CID — the previous version is not overwritten. This is intentional: CID-addressed storage is immutable. To support document versioning, store multiple CIDs in the database (one per version). This gives you a full, auditable version history without additional storage infrastructure.

Scenario 3 AI Model Artefacts & Knowledge Base

Business Context

Octopus AI agents generate and consume expensive computational artefacts: embedding vectors, model snapshots, knowledge base exports, and pre-computed retrieval indexes. Regenerating these artefacts on every run is computationally wasteful and slow. Persisting them in InfraHub Storage allows agents to check for the existence of a pre-computed artefact before triggering expensive generation — a form of content-addressed caching.

Because artefacts are identified by the SHA-256 of their content, the same model trained on the same dataset always produces the same CID. This enables deterministic cache hits across agent runs, deployments, and even tenants (when content is shared).

Implementation

public class KnowledgeBaseExportService
{
    private readonly IObjectStorageProvider _storage;
    private readonly IEmbeddingService _embedder;

    /// <summary>
    /// Returns the CID of the serialised knowledge snapshot.
    /// If an identical snapshot already exists, returns immediately — no re-upload.
    /// </summary>
    public async Task<string> ExportSnapshotAsync(KnowledgeBase kb, CancellationToken ct)
    {
        byte[] snapshotBytes = await _serialiser.SerialiseAsync(kb, ct);

        // StoreAsync is idempotent — identical bytes return the same CID
        string cid = await _storage.StoreAsync(
            snapshotBytes,
            "application/octet-stream",
            null,
            ct);

        return cid;
    }

    /// <summary>
    /// Check-before-compute pattern: only generate embeddings if not already stored.
    /// </summary>
    public async Task<byte[]> GetOrComputeEmbeddingAsync(
        string knownCid, Func<Task<byte[]>> computeFn, CancellationToken ct)
    {
        if (await _storage.ExistsAsync(knownCid, ct))
        {
            // Cache hit — retrieve without recomputing
            using var stream = await _storage.GetStreamAsync(knownCid, ct);
            return await stream.ReadAllBytesAsync(ct);
        }

        // Cache miss — compute and store for future calls
        byte[] computed = await computeFn();
        await _storage.StoreAsync(computed, "application/octet-stream", null, ct);
        return computed;
    }
}
CID-Addressable Caching The ExistsAsync check before recomputing is a first-class pattern for AI workloads. Embedding generation for large corpora can take minutes; storing the result and checking existence before recomputing reduces agent execution time significantly for repeated tasks.

Scenario 4 Workflow Attachment Pipeline

Business Context

Approval workflows frequently require supporting evidence — expense receipts, signed forms, photos of damage, purchase orders. In a Flow Studio pipeline, a user uploads a file at the start of the workflow; that file needs to be accessible to every subsequent node without being re-uploaded or embedded in the workflow state as raw bytes (which would bloat the workflow context). InfraHub Storage solves this by returning a CID that is lightweight enough to pass as a workflow variable.

The CID travels through the pipeline as a string. Any node downstream can call GetStreamAsync to retrieve the full file — for PDF rendering, email attachment, virus scanning, OCR, or any other processing step.

Pipeline Flow

File Upload
HTTP POST
StoreAsync
Returns CID
CID Variable
Workflow state
Approval Node
Human review
GetStreamAsync
Retrieve file
Email / Process
Downstream use

Implementation

// Node 1 — Upload handler (HTTP controller or flow node)
public async Task<string> HandleUploadAsync(
    IFormFile file, CancellationToken ct)
{
    using var ms = new MemoryStream();
    await file.CopyToAsync(ms, ct);
    byte[] bytes = ms.ToArray();

    string cid = await _storage.StoreAsync(bytes, file.ContentType, null, ct);

    // Store CID as a workflow variable — flows downstream automatically
    workflowContext.SetVariable("attachment_cid", cid);
    return cid;
}

// Node N — downstream node retrieving the attachment for processing
public async Task ProcessAttachmentAsync(
    WorkflowContext ctx, CancellationToken ct)
{
    string cid = ctx.GetVariable<string>("attachment_cid");

    using var fileStream = await _storage.GetStreamAsync(cid, ct);

    // Attach to outbound email, run OCR, virus scan, etc.
    await _emailService.SendWithAttachmentAsync(fileStream, ctx.ApproverEmail, ct);
}

Scenario 5 Multimedia & Media Assets

Business Context

Marketing and reporting workflows generate branded PDFs, data exports, chart images, and presentation decks. These assets are often rendered from templates — the same quarterly report layout applied to different periods or regions. When reports share significant template content, CID-based deduplication means identical byte sequences (unchanged sections, logo images, boilerplate pages) are stored once regardless of how many generated documents reference them at the application layer.

Media assets can be large — branded PDFs with embedded charts often exceed 10 MB, and some data export packages reach hundreds of megabytes. InfraHub Storage handles these through standard byte[] upload today, with stream-native support planned.

Implementation

public class ReportPublishingService
{
    private readonly IObjectStorageProvider _storage;

    public async Task<PublishedReport> PublishAsync(
        ReportRenderResult rendered, CancellationToken ct)
    {
        // Store the rendered PDF — dedup means re-running the same report
        // template for the same period returns the same CID instantly
        string pdfCid = await _storage.StoreAsync(
            rendered.PdfBytes,
            "application/pdf",
            null,
            ct);

        // Optionally store a thumbnail image alongside the PDF
        string thumbCid = await _storage.StoreAsync(
            rendered.ThumbnailPngBytes,
            "image/png",
            null,
            ct);

        return new PublishedReport
        {
            PdfCid       = pdfCid,
            ThumbnailCid = thumbCid,
            PublishedAt  = DateTime.UtcNow
        };
    }
}
Large File Warning — Current Implementation The current implementation buffers file content to a MemoryStream before uploading to MinIO. Files over 100 MB should be streamed at the network edge (CDN, reverse proxy) rather than routed through this API to avoid excessive memory pressure on the application container. A streaming StoreStreamAsync overload is planned for a future release.

Scenario 6 Audit Trail Evidence

Business Context

Compliance workflows — SOX, ISO 27001, GDPR data processing audits, financial regulatory filings — require timestamped, tamper-evident evidence records. This might be a screenshot of a system state at a point in time, a digitally signed approval PDF, or an exported log bundle. The key requirement is non-repudiation: evidence stored during an audit period must be provably unchanged when retrieved months later.

InfraHub Storage's immutability property is a perfect fit: because the CID is the SHA-256 hash of the file content, any attempt to silently replace or alter the stored file would produce a different CID — the mismatch is immediately detectable. Storing the CID in a compliance database alongside a timestamp creates an unforgeable link between the event and its evidence file.

Implementation

public class ComplianceEvidenceService
{
    private readonly IObjectStorageProvider _storage;
    private readonly IComplianceRepository _compliance;

    public async Task<EvidenceRecord> StoreEvidenceAsync(
        string auditEventID,
        byte[] evidenceBytes,
        string contentType,
        CancellationToken ct)
    {
        // Store evidence — returns a deterministic, immutable CID
        string cid = await _storage.StoreAsync(evidenceBytes, contentType, null, ct);

        var record = new EvidenceRecord
        {
            AuditEventID = auditEventID,
            FileCid       = cid,
            CapturedAt    = DateTime.UtcNow,
            // ContentHash = cid (the CID IS the SHA-256 hash — no separate hash field needed)
        };

        // Persist in the compliance database with timestamp
        await _compliance.RecordEvidenceAsync(record, ct);
        return record;
    }

    public async Task<bool> VerifyIntegrityAsync(
        EvidenceRecord record, CancellationToken ct)
    {
        // Retrieve the stored file and recompute its SHA-256
        using var stream = await _storage.GetStreamAsync(record.FileCid, ct);
        byte[] bytes = await stream.ReadAllBytesAsync(ct);
        string computedCid = _cidProvider.Compute(bytes);

        // If someone replaced the file, computedCid will not match record.FileCid
        return computedCid == record.FileCid;
    }
}
Integrity Verification The VerifyIntegrityAsync pattern re-downloads the stored file and recomputes the CID. If the computed CID matches the stored CID, the file is provably unmodified. This integrity check can be run periodically or as part of an audit export to confirm evidence is pristine.

Scenario 7 Data Export & Import Packages

Business Context

InstallHub, data migration pipelines, and tenant onboarding workflows produce ZIP packages containing configuration snapshots, seed data, and schema definitions. These packages are generated on-demand, often large, and have a predictable lifecycle: actively needed during an installation or migration, then dormant and archivable after 30 days, and deletable after a year.

InfraHub Storage's lifecycle archival rules (configured in MinIO — see the Archival & Lifecycle guide) handle this automatically: objects transition to cold storage at Day 30, reducing cost for the long tail of packages that are rarely accessed after the initial installation window.

Implementation

public class InstallPackageService
{
    private readonly IObjectStorageProvider _storage;

    public async Task<InstallManifest> PublishPackageAsync(
        InstallPackage pkg, CancellationToken ct)
    {
        // Serialise the installation package to a ZIP archive
        byte[] zipBytes = await _packager.BuildZipAsync(pkg, ct);

        string cid = await _storage.StoreAsync(
            zipBytes,
            "application/zip",
            null,
            ct);

        return new InstallManifest
        {
            PackageID   = pkg.ID,
            ArchiveCid  = cid,
            GeneratedAt = DateTime.UtcNow,
            // Lifecycle policy transitions this to cold storage at Day 30 automatically
            ExpiresAt   = DateTime.UtcNow.AddDays(365)
        };
    }

    public async Task<Stream> DownloadPackageAsync(
        string archiveCid, CancellationToken ct)
    {
        return await _storage.GetStreamAsync(archiveCid, ct);
    }
}
Automatic Lifecycle Archival Packages older than 30 days automatically move to cold-tier storage via MinIO lifecycle policies, reducing storage costs without any application code changes. Packages are fully retrievable from cold tier — retrieval is slower but the data is never deleted until Day 365. See the Archival & Lifecycle guide for policy configuration details.

Scenario 8 Multi-Tenant SaaS Isolation

Business Context

In a multi-tenant deployment — where dozens or hundreds of customer organisations share a single BizFirst instance — it is critical that Customer A cannot access Customer B's files, even accidentally. InfraHub Storage enforces this at the storage layer: when PrependTenantID: true, every object key is automatically prefixed with tenant_{tenantID}/ before the CID.

This means isolation is not an application-level concern that can be accidentally bypassed by a bug or missing access check. The storage key itself encodes the tenant boundary. A CID retrieved for tenant 42 (tenant_42/3a7f2e...) is a different object key than the same CID for tenant 99 (tenant_99/3a7f2e...), even if both contain identical content.

How TenantID Is Resolved

Developers never pass TenantID manually. It is resolved automatically from the platform's IAiSessionContextAccessor, which is populated per-request by the BizFirst authentication middleware from the authenticated session token. This ensures:

Key Prefix Table

TenantIDObject Key in MinIOAccessible by Tenant 42?Accessible by Tenant 99?
42 tenant_42/3a7f2e8d... Yes No
99 tenant_99/3a7f2e8d... No Yes
42 tenant_42/b81c90f2... Yes No

Implementation

// Application code — TenantID is never mentioned. The platform handles it.
public class TenantDocumentService
{
    private readonly IObjectStorageProvider _storage;
    // IAiSessionContextAccessor is injected internally by IObjectStorageProvider
    // Application services do NOT need to inject or handle it.

    public async Task<string> StoreForCurrentTenantAsync(
        byte[] data, string contentType, CancellationToken ct)
    {
        // PrependTenantID=true means the stored key will be:
        // tenant_{currentTenantID}/{sha256_of_data}
        // No explicit TenantID parameter — isolation is automatic.
        return await _storage.StoreAsync(data, contentType, null, ct);
    }

    public async Task<Stream> GetForCurrentTenantAsync(
        string cid, CancellationToken ct)
    {
        // Only retrieves objects under the current tenant's prefix.
        // A CID belonging to a different tenant will return null / not found.
        return await _storage.GetStreamAsync(cid, ct);
    }
}
Zero-Trust Isolation Tenant isolation does not depend on every developer remembering to filter by TenantID. It is enforced automatically at the key-construction layer inside IObjectStorageProvider. Even if application code has a bug that passes a CID from the wrong tenant, the key prefix mismatch will cause a not-found result — not a cross-tenant data leak.

Storage Cost Estimation

The table below provides rough size and volume estimates for common scenarios. Use these as a planning baseline when sizing your MinIO disk allocation or estimating cloud storage spend. Actual figures depend on document complexity, image resolution, and compression ratios.

Scenario Typical File Size Monthly Volume Estimated Storage / Month
Invoice PDFs 150 KB 10,000 files ~1.5 GB
HR Documents 500 KB 500 files ~250 MB
AI Artefacts 2 MB 200 files ~400 MB
Workflow Attachments 1 MB 2,000 files ~2 GB
Multimedia / Reports 5 MB 400 files ~2 GB
Audit Evidence 200 KB 1,000 files ~200 MB
Data Export Packages 20 MB 50 packages ~1 GB
Deduplication Reduces Real Storage Significantly With content-addressable deduplication, actual storage is often 30–70% lower than naive estimates. The same contract template stored for 100 employees occupies the space of one copy — only truly unique bytes are stored. For invoice workflows where many invoices share a common layout, header, and footer, the savings are compounded across every billing cycle. Factor in at least a 40% deduplication discount when sizing disk for template-heavy workflows.

Cloud Provider Monthly Pricing Reference (approximate)

BackendStorage PriceEgress PriceNotes
MinIO (self-hosted) Cost of disk hardware / cloud VM disk Internal network only Lowest total cost for high-volume on-prem workloads
AWS S3 (Standard) ~$0.023 / GB ~$0.09 / GB Switch to S3-IA for infrequently accessed archives
Cloudflare R2 ~$0.015 / GB $0.00 (free egress) Best cost profile for download-heavy scenarios
Backblaze B2 ~$0.006 / GB ~$0.01 / GB Lowest storage cost for backup and archival targets