| | | 1 | | using System.Collections.ObjectModel; |
| | | 2 | | using AsiBackbone.Core.Audit; |
| | | 3 | | |
| | | 4 | | namespace AsiBackbone.Core.HostIntegration; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Creates append-only audit lifecycle events that bind a governed decision to host execution evidence. |
| | | 8 | | /// </summary> |
| | | 9 | | public static class HostAccountabilityLifecycleEvent |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// Creates a gateway-execution-started lifecycle event for one logical operation and attempt. |
| | | 13 | | /// </summary> |
| | | 14 | | public static AuditResidueLifecycleEvent ExecutionStarted( |
| | | 15 | | IAsiBackboneAuditResidue residue, |
| | | 16 | | string operationExecutionId, |
| | | 17 | | string? executionAttemptId = null, |
| | | 18 | | string? decisionAuditRecordId = null, |
| | | 19 | | string? eventId = null, |
| | | 20 | | DateTimeOffset? occurredUtc = null, |
| | | 21 | | IReadOnlyDictionary<string, string>? metadata = null) |
| | | 22 | | { |
| | 0 | 23 | | ArgumentNullException.ThrowIfNull(residue); |
| | 0 | 24 | | ArgumentException.ThrowIfNullOrWhiteSpace(operationExecutionId); |
| | | 25 | | |
| | 0 | 26 | | SortedDictionary<string, string> lifecycleMetadata = new(StringComparer.Ordinal); |
| | 0 | 27 | | AddMetadata(lifecycleMetadata, metadata); |
| | 0 | 28 | | lifecycleMetadata[HostAccountabilityMetadataKeys.OperationExecutionId] = operationExecutionId.Trim(); |
| | 0 | 29 | | AddOptional(lifecycleMetadata, HostAccountabilityMetadataKeys.ExecutionAttemptId, executionAttemptId); |
| | 0 | 30 | | AddOptional(lifecycleMetadata, HostAccountabilityMetadataKeys.DecisionAuditRecordId, decisionAuditRecordId); |
| | | 31 | | |
| | 0 | 32 | | return AuditResidueLifecycleEvent.FromResidue( |
| | 0 | 33 | | AuditResidueLifecycleStage.GatewayExecutionStarted, |
| | 0 | 34 | | residue, |
| | 0 | 35 | | eventId: eventId, |
| | 0 | 36 | | occurredUtc: occurredUtc, |
| | 0 | 37 | | outcome: "Started", |
| | 0 | 38 | | metadata: new ReadOnlyDictionary<string, string>(lifecycleMetadata)); |
| | | 39 | | } |
| | | 40 | | |
| | | 41 | | /// <summary> |
| | | 42 | | /// Creates a gateway-execution-completed lifecycle event from a typed host execution receipt. |
| | | 43 | | /// </summary> |
| | | 44 | | /// <remarks> |
| | | 45 | | /// The existing completion stage remains stable. Committed, failed, rolled-back, and no-mutation |
| | | 46 | | /// distinctions are carried by the typed receipt outcome and stable metadata keys rather than by |
| | | 47 | | /// expanding the lifecycle-stage enum. |
| | | 48 | | /// </remarks> |
| | | 49 | | public static AuditResidueLifecycleEvent FromExecutionReceipt( |
| | | 50 | | IAsiBackboneAuditResidue residue, |
| | | 51 | | GovernedOperationExecutionReceipt receipt, |
| | | 52 | | string? eventId = null, |
| | | 53 | | IReadOnlyDictionary<string, string>? metadata = null) |
| | | 54 | | { |
| | 20 | 55 | | ArgumentNullException.ThrowIfNull(residue); |
| | 20 | 56 | | ArgumentNullException.ThrowIfNull(receipt); |
| | | 57 | | |
| | 20 | 58 | | return AuditResidueLifecycleEvent.FromResidue( |
| | 20 | 59 | | AuditResidueLifecycleStage.GatewayExecutionCompleted, |
| | 20 | 60 | | residue, |
| | 20 | 61 | | eventId: eventId, |
| | 20 | 62 | | occurredUtc: receipt.CompletedUtc, |
| | 20 | 63 | | outcome: receipt.PersistenceOutcome.ToString(), |
| | 20 | 64 | | metadata: receipt.ToLifecycleMetadata(metadata)); |
| | | 65 | | } |
| | | 66 | | |
| | | 67 | | private static void AddMetadata(SortedDictionary<string, string> destination, IReadOnlyDictionary<string, string>? m |
| | | 68 | | { |
| | 0 | 69 | | if (metadata is null) |
| | | 70 | | { |
| | 0 | 71 | | return; |
| | | 72 | | } |
| | | 73 | | |
| | 0 | 74 | | foreach (KeyValuePair<string, string> item in metadata) |
| | | 75 | | { |
| | 0 | 76 | | if (!string.IsNullOrWhiteSpace(item.Key)) |
| | | 77 | | { |
| | 0 | 78 | | destination[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty; |
| | | 79 | | } |
| | | 80 | | } |
| | 0 | 81 | | } |
| | | 82 | | |
| | | 83 | | private static void AddOptional(SortedDictionary<string, string> destination, string key, string? value) |
| | | 84 | | { |
| | 0 | 85 | | if (!string.IsNullOrWhiteSpace(value)) |
| | | 86 | | { |
| | 0 | 87 | | destination[key] = value.Trim(); |
| | | 88 | | } |
| | 0 | 89 | | } |
| | | 90 | | } |