Skip to content

Replace listening-directory handoff with durable file storage #796

Description

@StuartFerguson

Summary

Replace the current physical ListeningDirectory handoff with durable file storage (blob/object storage or an equivalent durable inbox), while continuing to use EventStore for file metadata, line events, and processing state.

The intended flow is:

Watcher -> upload API -> durable blob/inbox -> EventStore file metadata and line events -> transaction processing

Problem

The current upload workflow writes the multipart upload to TemporaryFileLocation, moves it into the profile ListeningDirectory, reads it back from disk during processing, and finally moves it to processed or failed.

This creates an unnecessary filesystem handoff and makes recovery dependent on a local path. It also leaves consistency gaps if the file move succeeds but EventStore persistence fails, or if the process restarts between upload, file creation, line ingestion, and transaction dispatch.

FileProfileDirectoryRecoveryService currently recovers only files left under inprogress; it does not provide a durable, resumable source for every accepted upload.

Proposed change

Introduce a durable file-content store abstraction, for example IUploadedFileStore, with operations to:

  • stream an upload into durable storage;
  • return a content URI/key and SHA-256 hash;
  • open the stored file for streaming reads;
  • retain the file according to a configurable retention policy;
  • delete/archive content only after the file lifecycle is complete.

Update the upload and processing workflow so that:

  1. The API streams the multipart content directly into durable storage rather than writing to the temporary/listening directory.
  2. The content hash is calculated during the upload and remains the canonical FileId validation source.
  3. EventStore records the file metadata, content key, hash, upload state, and processing state.
  4. File lines are parsed from the durable stream and recorded with stable file/line identity.
  5. Transaction retries do not require the original local filesystem path.
  6. A failed or interrupted upload can be identified and resumed, retried, or quarantined.
  7. Original file content remains available for audit and reconciliation until retention expiry.

Required reliability behavior

  • Idempotent handling of repeated watcher submissions for the same FileId.
  • Explicit states for at least Uploading, Accepted, Processing, Completed, CompletedWithErrors, Failed, and Quarantined.
  • Recovery of uploads interrupted at any stage, including application restart.
  • Retry and reconciliation for transaction processor timeouts where the remote outcome is unknown.
  • No transaction dispatch before the file content and file metadata are durably accepted.
  • No loss of duplicate detail rows that happen to have identical text.
  • Configurable retention and cleanup with audit-safe failure handling.

API changes

  • Replace path-based internal commands with a stream/content-reference based contract.
  • Return a stable upload/processing identifier and current state.
  • Add status and reprocess/retry operations for files and, where appropriate, individual lines.
  • Return clear validation errors for malformed files and incomplete uploads.

Out of scope

  • Adding new file formats.
  • Changing transaction business rules.
  • Replacing EventStore as the source of domain state.
  • Removing the watcher responsibility for detecting source files.

Acceptance criteria

  • No normal processing path requires ListeningDirectory, TemporaryFileLocation, or a local file path.
  • A file can be uploaded, processed, retried, and reconciled after application restart.
  • Repeated delivery of the same file does not create duplicate file aggregates or transactions.
  • A lost transaction response cannot silently create a second transaction on retry.
  • Original content can be retrieved for audit until the configured retention period expires.
  • Unit tests cover the storage abstraction, interrupted uploads, duplicate submissions, and cleanup failures.
  • Integration tests cover upload-to-processing, restart recovery, retry, and reconciliation using the chosen durable store.
  • Existing file profile, import-log, and file-details APIs continue to expose accurate lifecycle and line-processing results.

Related issues

Activity

  1. StuartFerguson commented on Sep 17, 2026

    @StuartFerguson
    MemberAuthor

    Local durable inbox option

    The durable storage proposed in this issue does not have to be cloud blob storage. For a single-host deployment, the existing local-directory approach can be retained and hardened into a properly managed local inbox.

    Recommended structure:

    {root}/uploading/
    {root}/accepted/
    {root}/processing/
    {root}/completed/
    {root}/failed/
    {root}/quarantine/
    

    The local inbox implementation should:

    • stream uploads into an uploading temporary file;
    • flush the completed file to disk before acknowledging acceptance;
    • use a generated name based on FileId and content hash rather than the client filename;
    • atomically rename the file into accepted only after the write is complete;
    • persist the file key, hash, size, original name, and lifecycle state in EventStore;
    • use exclusive access or a lease so only one worker processes a file;
    • move files atomically between lifecycle directories;
    • reconcile directory contents against EventStore on startup;
    • recover files left in uploading, accepted, or processing after a crash;
    • make repeated submissions with the same FileId and hash idempotent;
    • quarantine files with invalid content, mismatched hashes, or unrecoverable state;
    • monitor disk capacity, failed moves, stale files, and cleanup failures;
    • define retention, archival, and backup/restore policies.

    The key change is to treat the local filesystem as a durable inbox with an explicit state machine, rather than as an incidental temporary path. EventStore should remain the source of processing state, while the local inbox remains the durable source of original file content.

    This option is appropriate for a single stable host. A blob/object-store implementation can later provide the same IUploadedFileStore contract if the service becomes multi-instance or cloud-hosted.

  2. linked a pull request that will close this issueFix upload temporary file handling #828on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions