{"record":{"id":"5bd32f525d161c54","repo":"fullstackhero/dotnet-starter-kit","slug":"no-policy-deletefilecommandhandler","errorCode":null,"errorMessage":"no policy","messagePattern":"no policy","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/DeleteFile/DeleteFileCommandHandler.cs","lineNumber":29,"sourceCode":"\npublic sealed class DeleteFileCommandHandler(\n    FilesDbContext db,\n    FileAccessPolicyRegistry policies,\n    ICurrentUser currentUser)\n    : ICommandHandler<DeleteFileCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(DeleteFileCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n\n        var f = await db.FileAssets\n            .FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"file not found\");\n\n        var userId = currentUser.GetUserId().ToString();\n        var policy = policies.Resolve(f.OwnerType)\n            ?? throw new ForbiddenException(\"no policy\");\n        var ctx = new FileAccessContext(f.Id, f.OwnerType, f.OwnerId, f.CreatedByUserId, (int)f.Visibility);\n        if (!await policy.CanDeleteAsync(ctx, userId, cancellationToken).ConfigureAwait(false))\n        {\n            throw new ForbiddenException(\"not allowed to delete this file\");\n        }\n\n        // Soft-delete: AuditableEntitySaveChangesInterceptor sets IsDeleted/DeletedOnUtc/DeletedBy on\n        // Remove() for ISoftDeletable; byte purge runs later via PurgeDeletedFilesJob post-retention.\n        db.FileAssets.Remove(f);\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":11,"sourceCodeEnd":43,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/DeleteFile/DeleteFileCommandHandler.cs#L11-L43","documentation":"The delete path resolves the access policy for the file's OwnerType via FileAccessPolicyRegistry and throws ForbiddenException(\"no policy\") when resolution returns null. Like the visibility path, this means no IFileAccessPolicy is registered for that OwnerType — a wiring/registration gap, not a user authorization failure.","triggerScenarios":"FileAssets row with an OwnerType that has no registered IFileAccessPolicy; DI registration missing or module not loaded; OwnerType value in data does not match any policy key (typo, renamed constant, hand-edited seed row).","commonSituations":"New owner type shipped without its delete policy; integration environment missing a module registration; data imported from another system with unrecognized OwnerType strings; policy class removed during refactor while data still references it.","solutions":["Register the missing IFileAccessPolicy implementation for that OwnerType in the DI container.","Inspect the row's OwnerType and reconcile it with the set of policy keys known to FileAccessPolicyRegistry.","Add a registration coverage test/startup assertion covering all OwnerType values.","Repair or remove rows carrying invalid OwnerType values via a data fix."],"exampleFix":"// before\n// OwnerType \"Archive\" unregistered -> 403 \"no policy\" on delete\n// after\nservices.AddSingleton<IFileAccessPolicy, ArchiveFileAccessPolicy>();","handlingStrategy":"validation","validationCode":"var ownerTypes = new[] { \"User\", \"Organization\" /* all valid values */ };\nif (!ownerTypes.Contains(asset.OwnerType)) throw new InvalidOperationException($\"No delete policy registered for OwnerType '{asset.OwnerType}'.\");","typeGuard":"bool HasPolicy(FileAccessPolicyRegistry registry, string ownerType) => registry.Resolve(ownerType) is not null;","tryCatchPattern":"catch (ForbiddenException e) when (e.Message == \"no policy\") {\n    logger.LogError(e, \"Missing delete policy for OwnerType on file {FileId}\", fileId);\n    return Results.Problem(\"Server misconfiguration: delete policy missing.\", statusCode: 500);\n}","preventionTips":["Ship the delete policy together with any new OwnerType.","Startup-check registry coverage of all owner types.","Validate OwnerType values in import/seed pipelines.","Unit-test registry resolution per owner type."],"tags":["configuration","di","authorization","files"],"backgroundTag":"missing-config-key","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"}