{"record":{"id":"e7cd1ac2c5493e64","repo":"fullstackhero/dotnet-starter-kit","slug":"actual-size-must-be-positive","errorCode":null,"errorMessage":"Actual size must be positive.","messagePattern":"Actual size must be positive\\.","errorType":"exception","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Domain/FileAsset.cs","lineNumber":94,"sourceCode":"            ScanStatus = ScanStatus.NotScanned,\n            UploadDeadline = uploadDeadline,\n            CreatedByUserId = createdByUserId,\n            CreatedAtUtc = DateTime.UtcNow\n        };\n    }\n\n    public void MarkAvailable(long actualSize, ScanStatus scanResult)\n    {\n        if (Status != FileAssetStatus.PendingUpload)\n        {\n            throw new CustomException(\n                $\"Cannot finalize file in status {Status}.\",\n                errors: null,\n                HttpStatusCode.Conflict);\n        }\n        if (actualSize <= 0)\n        {\n            throw new ArgumentOutOfRangeException(nameof(actualSize), \"Actual size must be positive.\");\n        }\n\n        SizeBytes = actualSize;\n        ScanStatus = scanResult;\n        Status = scanResult == ScanStatus.Infected ? FileAssetStatus.Quarantined : FileAssetStatus.Available;\n        UploadDeadline = null;\n        UpdatedAtUtc = DateTime.UtcNow;\n\n        AddDomainEvent(DomainEvent.Create((id, ts) =>\n            new FileFinalizedDomainEvent(Id, OwnerType, OwnerId, Status, id, ts)));\n    }\n\n    /// <summary>Reverses a soft delete. Idempotent.</summary>\n    public void Restore()\n    {\n        if (!IsDeleted) return;\n        IsDeleted = false;\n        DeletedOnUtc = null;","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Domain/FileAsset.cs#L76-L112","documentation":"MarkAvailable validates that the actual size reported after upload is > 0 and throws ArgumentOutOfRangeException otherwise. A zero/negative actual size means the stored blob is empty or the size was misreported, which would corrupt quota accounting and download semantics.","triggerScenarios":"Completing an upload where the storage object is 0 bytes (client PUT an empty body, partial/multipart upload failed silently), or the provider-reported ETag/size lookup returned 0.","commonSituations":"Client aborted the PUT halfway leaving an empty object; wrong storage key passed to the stat call so the size read defaults to 0; uploading an empty file that passed (or bypassed) the declared-size check.","solutions":["Verify on the client that the uploaded blob size matches before calling complete; re-PUT the file if size is 0.","Confirm the storage key used for the size probe is the same key the client uploaded to.","Block zero-byte files at selection time in the UI.","If the provider reports 0 after a successful PUT, check MinIO/S3 configuration (multipart completion, bucket policy)."],"exampleFix":"// before\nawait putToPresignedUrl(file);\nawait api.post(`/files/${id}/complete`); // may 500/422: actual size 0\n// after\nawait putToPresignedUrl(file);\nif (file.size <= 0) { showError('empty file'); return; }\nawait api.post(`/files/${id}/complete`);","handlingStrategy":"validation","validationCode":"if (!file || file.size <= 0) { showError('empty file'); return; }\nawait putToPresignedUrl(file);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify uploaded blob size matches File.size before completing","Block zero-byte files at selection","Ensure the storage probe uses the same key the client uploaded to"],"tags":["files","upload","argument","size"],"backgroundTag":"argument-out-of-range","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}