namespace StellaOps.AuditPack.Services; public sealed partial class AuditBundleReader { /// /// Reads and verifies an audit bundle. /// public async Task ReadAsync( AuditBundleReadRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ArgumentException.ThrowIfNullOrWhiteSpace(request.BundlePath); if (!File.Exists(request.BundlePath)) { return AuditBundleReadResult.Failed("Bundle file not found"); } var tempDir = CreateTempDir("audit-read"); try { await ExtractBundleAsync(request.BundlePath, tempDir, cancellationToken).ConfigureAwait(false); var manifestLoad = await LoadManifestAsync(tempDir, cancellationToken).ConfigureAwait(false); if (!manifestLoad.Success || manifestLoad.Manifest is null || manifestLoad.ManifestBytes is null) { return AuditBundleReadResult.Failed(manifestLoad.Error ?? "Failed to parse manifest"); } var result = new AuditBundleReadResult { Success = true, Manifest = manifestLoad.Manifest, BundleDigest = await ComputeFileDigestAsync(request.BundlePath, cancellationToken).ConfigureAwait(false), ExtractedPath = request.ExtractToPath is not null ? null : tempDir }; var signature = await ApplySignatureVerificationAsync( result, request, manifestLoad.ManifestBytes, tempDir, cancellationToken) .ConfigureAwait(false); if (signature.Stop) { return signature.Result; } result = signature.Result; var merkle = await ApplyMerkleVerificationAsync( result, request, manifestLoad.Manifest, tempDir, cancellationToken) .ConfigureAwait(false); if (merkle.Stop) { return merkle.Result; } result = merkle.Result; var digests = await ApplyInputDigestVerificationAsync( result, request, manifestLoad.Manifest, tempDir, cancellationToken) .ConfigureAwait(false); if (digests.Stop) { return digests.Result; } result = digests.Result; var extraction = HandleExtraction(result, request, tempDir); if (!extraction.Success) { return extraction.Result with { Success = false, Error = extraction.Error }; } result = extraction.Result; tempDir = extraction.TempDir; if (request.LoadReplayInputs) { var extractPath = result.ExtractedPath ?? tempDir; var inputs = await LoadReplayInputsAsync(extractPath, cancellationToken) .ConfigureAwait(false); result = result with { ReplayInputs = inputs }; } return result; } catch (Exception ex) { return AuditBundleReadResult.Failed($"Failed to read bundle: {ex.Message}"); } } }