|
| 1 | +namespace Sigtran.NET.Core.Utilities; |
| 2 | + |
| 3 | +/// <summary> |
| 4 | +/// Describes one commercial evidence run approval checklist item. |
| 5 | +/// </summary> |
| 6 | +public sealed class SigtranCommercialEvidenceRunApprovalChecklistItem |
| 7 | +{ |
| 8 | + /// <summary>Creates a commercial evidence run approval checklist item.</summary> |
| 9 | + /// <param name="id">The stable checklist item identifier.</param> |
| 10 | + /// <param name="description">The checklist item description.</param> |
| 11 | + /// <param name="required">Whether the item is required for approval.</param> |
| 12 | + /// <param name="satisfied">Whether the item is satisfied.</param> |
| 13 | + public SigtranCommercialEvidenceRunApprovalChecklistItem( |
| 14 | + string id, |
| 15 | + string description, |
| 16 | + bool required, |
| 17 | + bool satisfied) |
| 18 | + { |
| 19 | + Id = string.IsNullOrWhiteSpace(id) ? throw new ArgumentException("Checklist item id is required.", nameof(id)) : id; |
| 20 | + Description = string.IsNullOrWhiteSpace(description) ? throw new ArgumentException("Checklist item description is required.", nameof(description)) : description; |
| 21 | + Required = required; |
| 22 | + Satisfied = satisfied; |
| 23 | + } |
| 24 | + |
| 25 | + /// <summary>The stable checklist item identifier.</summary> |
| 26 | + public string Id { get; } |
| 27 | + |
| 28 | + /// <summary>The checklist item description.</summary> |
| 29 | + public string Description { get; } |
| 30 | + |
| 31 | + /// <summary>Whether the item is required for approval.</summary> |
| 32 | + public bool Required { get; } |
| 33 | + |
| 34 | + /// <summary>Whether the item is satisfied.</summary> |
| 35 | + public bool Satisfied { get; } |
| 36 | + |
| 37 | + /// <summary>Whether the item allows approval to proceed.</summary> |
| 38 | + public bool AllowsApproval => !Required || Satisfied; |
| 39 | +} |
| 40 | + |
| 41 | +/// <summary> |
| 42 | +/// Describes the approval checklist for a commercial evidence run. |
| 43 | +/// </summary> |
| 44 | +public sealed class SigtranCommercialEvidenceRunApprovalChecklist |
| 45 | +{ |
| 46 | + /// <summary>Creates a commercial evidence run approval checklist.</summary> |
| 47 | + /// <param name="target">The commercial evidence run approval target.</param> |
| 48 | + /// <param name="items">The checklist items.</param> |
| 49 | + public SigtranCommercialEvidenceRunApprovalChecklist( |
| 50 | + SigtranCommercialEvidenceApprovedRunTarget target, |
| 51 | + IReadOnlyList<SigtranCommercialEvidenceRunApprovalChecklistItem> items) |
| 52 | + { |
| 53 | + Target = target ?? throw new ArgumentNullException(nameof(target)); |
| 54 | + ArgumentNullException.ThrowIfNull(items); |
| 55 | + Items = items.Count == 0 ? throw new ArgumentException("At least one checklist item is required.", nameof(items)) : items.ToArray(); |
| 56 | + } |
| 57 | + |
| 58 | + /// <summary>The commercial evidence run approval target.</summary> |
| 59 | + public SigtranCommercialEvidenceApprovedRunTarget Target { get; } |
| 60 | + |
| 61 | + /// <summary>The checklist items.</summary> |
| 62 | + public IReadOnlyList<SigtranCommercialEvidenceRunApprovalChecklistItem> Items { get; } |
| 63 | + |
| 64 | + /// <summary>Whether every required checklist item is satisfied.</summary> |
| 65 | + public bool AllRequiredItemsSatisfied => Items |
| 66 | + .Where(static item => item.Required) |
| 67 | + .All(static item => item.Satisfied); |
| 68 | + |
| 69 | + /// <summary>Whether checklist item identifiers are unique.</summary> |
| 70 | + public bool UsesUniqueItemIds => Items |
| 71 | + .Select(static item => item.Id) |
| 72 | + .Distinct(StringComparer.OrdinalIgnoreCase) |
| 73 | + .Count() == Items.Count; |
| 74 | + |
| 75 | + /// <summary>Whether the checklist explicitly covers reviewer approval.</summary> |
| 76 | + public bool IncludesReviewerApproval => Items.Any(static item => string.Equals(item.Id, "reviewer-approval-recorded", StringComparison.OrdinalIgnoreCase)); |
| 77 | + |
| 78 | + /// <summary>Whether the checklist explicitly covers redaction protection.</summary> |
| 79 | + public bool IncludesRedactionProtection => Items.Any(static item => string.Equals(item.Id, "trace-redaction-approved", StringComparison.OrdinalIgnoreCase)); |
| 80 | + |
| 81 | + /// <summary>Whether the checklist is ready for reviewer manifest creation.</summary> |
| 82 | + public bool IsReady => Target.IsReadyForApproval |
| 83 | + && UsesUniqueItemIds |
| 84 | + && IncludesReviewerApproval |
| 85 | + && IncludesRedactionProtection |
| 86 | + && AllRequiredItemsSatisfied; |
| 87 | + |
| 88 | + /// <summary>Returns the unsatisfied required checklist item identifiers.</summary> |
| 89 | + /// <returns>The unsatisfied required checklist item identifiers.</returns> |
| 90 | + public IReadOnlyList<string> GetUnsatisfiedRequiredItemIds() |
| 91 | + { |
| 92 | + return Items |
| 93 | + .Where(static item => item.Required && !item.Satisfied) |
| 94 | + .Select(static item => item.Id) |
| 95 | + .ToArray(); |
| 96 | + } |
| 97 | + |
| 98 | + /// <summary>Formats a compact approval checklist summary.</summary> |
| 99 | + /// <returns>The approval checklist summary.</returns> |
| 100 | + public string Describe() |
| 101 | + { |
| 102 | + return $"commercialEvidenceRunApprovalChecklistReady={IsReady} items={Items.Count} unsatisfied={GetUnsatisfiedRequiredItemIds().Count}"; |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +/// <summary> |
| 107 | +/// Provides commercial evidence run approval checklist helpers. |
| 108 | +/// </summary> |
| 109 | +public static class SigtranCommercialEvidenceRunApprovalChecklists |
| 110 | +{ |
| 111 | + /// <summary>Creates the default approval checklist for a commercial evidence run.</summary> |
| 112 | + /// <param name="target">The commercial evidence run approval target.</param> |
| 113 | + /// <param name="reviewerApprovalRecorded">Whether reviewer approval has been recorded.</param> |
| 114 | + /// <returns>The commercial evidence run approval checklist.</returns> |
| 115 | + public static SigtranCommercialEvidenceRunApprovalChecklist CreateDefault( |
| 116 | + SigtranCommercialEvidenceApprovedRunTarget target, |
| 117 | + bool reviewerApprovalRecorded = true) |
| 118 | + { |
| 119 | + ArgumentNullException.ThrowIfNull(target); |
| 120 | + SigtranCommercialEvidenceFileSystemPromotionExecution promotion = target.PromotionExecution; |
| 121 | + |
| 122 | + return new( |
| 123 | + target, |
| 124 | + [ |
| 125 | + new("run-target-ready", "Run target identity, timing, source commit, and promotion execution are ready.", required: true, target.IsReadyForApproval), |
| 126 | + new("filesystem-promotion-ready", "Filesystem-backed evidence promotion is ready.", required: true, promotion.IsReady), |
| 127 | + new("verification-report-verified", "The filesystem-backed file verification report is verified.", required: true, promotion.AttachmentExecution.SealExecution.LedgerExecution.Ledger.Report.IsVerified), |
| 128 | + new("retention-ledger-ready", "The retention ledger is ready.", required: true, promotion.AttachmentExecution.SealExecution.LedgerExecution.Ledger.IsReady), |
| 129 | + new("integrity-seal-ready", "The integrity seal is ready.", required: true, promotion.AttachmentExecution.SealExecution.Seal.IsReady), |
| 130 | + new("publication-attachments-ready", "Publication attachments are ready.", required: true, promotion.AttachmentExecution.AttachmentManifest.IsReady), |
| 131 | + new("trace-redaction-approved", "Trace-bearing publication attachments have approved redaction state.", required: true, promotion.AttachmentExecution.ProtectsTraceBearingArtifacts), |
| 132 | + new("promotion-gate-approved", "The filesystem-backed promotion gate is explicitly approved.", required: true, promotion.PromotionApproved), |
| 133 | + new("reviewer-approval-recorded", "A reviewer approval record is present.", required: true, reviewerApprovalRecorded) |
| 134 | + ]); |
| 135 | + } |
| 136 | +} |
0 commit comments