using System.Globalization; using System.Text.Json; using MBN_STOCK_WEBVIEW.Infrastructure; using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW; public sealed partial class MainWindow { private const string ManualFinancialCorrelationQuarantineMessage = "A manual-financial database write result could not be confirmed. Restart the app and reconcile INPUT_* before another write."; private readonly SemaphoreSlim _manualFinancialWriteGate = new(1, 1); private readonly object _manualFinancialReadLanesGate = new(); private readonly Dictionary _manualFinancialReadLanes = new(StringComparer.Ordinal); private IManualFinancialScreenService? _manualFinancialScreenService; private CancellationTokenSource? _manualFinancialWriteCancellation; private string? _manualFinancialInitializationError; private string? _manualFinancialWriteQuarantineMessage; private int _manualFinancialRuntimeState; private int _manualFinancialWriteInFlight; private int _manualFinancialWriteQuarantined; private int _manualFinancialBrowserGeneration; /// /// Root integration hook. Call after InitializeDatabaseRuntime. Every request /// handler also calls it lazily, so an omitted eager call fails closed. /// private void InitializeManualFinancialRuntime() { if (Volatile.Read(ref _manualFinancialRuntimeState) != 0) { return; } var runtime = _databaseRuntime; if (runtime is null || Interlocked.CompareExchange(ref _manualFinancialRuntimeState, 1, 0) != 0) { return; } try { _manualFinancialScreenService = new LegacyManualFinancialScreenService( runtime.Executor, new OracleManualFinancialMutationExecutor( runtime.ConnectionFactory, runtime.Options.Resilience)); } catch { _manualFinancialScreenService = null; _manualFinancialInitializationError = "The manual-financial Oracle boundary could not be initialized."; Volatile.Write(ref _manualFinancialRuntimeState, -1); } } /// /// Root WebMessage hook. True means the request belongs to this boundary, /// including malformed payloads which receive an INVALID_REQUEST response. /// private bool TryHandleManualFinancialRequest(string? requestType, JsonElement payload) { switch (requestType) { case "request-manual-financial-list": _ = HandleManualFinancialListRequestAsync(payload); return true; case "request-manual-financial-load": _ = HandleManualFinancialLoadRequestAsync(payload); return true; case "create-manual-financial-record": _ = HandleManualFinancialCreateRequestAsync(payload); return true; case "update-manual-financial-record": _ = HandleManualFinancialUpdateRequestAsync(payload); return true; case "delete-manual-financial-record": _ = HandleManualFinancialDeleteRequestAsync(payload); return true; case "delete-all-manual-financial-records": _ = HandleManualFinancialDeleteAllRequestAsync(payload); return true; case "request-manual-financial-selection": _ = HandleManualFinancialSelectionRequestAsync(payload); return true; default: return false; } } private async Task HandleManualFinancialListRequestAsync(JsonElement payload) { if (!TryParseManualFinancialListRequest( payload, out var requestId, out var screen, out var query, out var maximumResults)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "list", SafeManualFinancialScreen(payload), query: SafeManualFinancialQuery(payload)); return; } InitializeManualFinancialRuntime(); var service = _manualFinancialScreenService; if (service is null) { PostManualFinancialUnavailable(requestId, "list", ToWireValue(screen), query: query); return; } await ExecuteManualFinancialReadAsync( requestId, "list", screen, query, string.Empty, async cancellationToken => { var result = await service.SearchAsync( screen, query, maximumResults, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); return new ManualFinancialBridgeReply( "manual-financial-list-results", new { requestId, screen = ToWireValue(screen), query = result.Query, result.RetrievedAt, totalRowCount = result.Items.Count, truncated = result.IsTruncated, items = result.Items.Select(CreateManualFinancialSnapshotPayload) }); }); } private async Task HandleManualFinancialLoadRequestAsync(JsonElement payload) { if (!TryParseManualFinancialIdentityRequest( payload, ["requestId", "screen", "stockName"], out var requestId, out var identity)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "load", SafeManualFinancialScreen(payload), stockName: SafeManualFinancialStockName(payload)); return; } InitializeManualFinancialRuntime(); var service = _manualFinancialScreenService; if (service is null) { PostManualFinancialUnavailable( requestId, "load", ToWireValue(identity.Screen), stockName: identity.StockName); return; } await ExecuteManualFinancialReadAsync( requestId, "load", identity.Screen, string.Empty, identity.StockName, async cancellationToken => { var snapshot = await service.GetAsync(identity, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); return new ManualFinancialBridgeReply( "manual-financial-load-results", new { requestId, screen = ToWireValue(identity.Screen), retrievedAt = DateTimeOffset.UtcNow, snapshot = CreateManualFinancialSnapshotPayload(snapshot) }); }); } private async Task HandleManualFinancialSelectionRequestAsync(JsonElement payload) { if (!TryParseManualFinancialSelectionRequest( payload, out var requestId, out var identity, out var rowVersion, out var stock)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "selection", SafeManualFinancialScreen(payload), stockName: SafeManualFinancialStockName(payload)); return; } InitializeManualFinancialRuntime(); var service = _manualFinancialScreenService; if (service is null || _stockSearchService is null) { PostManualFinancialUnavailable( requestId, "selection", ToWireValue(identity.Screen), stockName: identity.StockName); return; } await ExecuteManualFinancialReadAsync( requestId, "selection", identity.Screen, string.Empty, identity.StockName, async cancellationToken => { var snapshot = await service.GetAsync(identity, cancellationToken); EnsureManualFinancialRowVersion(snapshot, rowVersion); var verified = await ResolveManualFinancialStockAsync( identity, stock, cancellationToken); var result = ManualFinancialCutContracts.CreateSelection(snapshot, verified); cancellationToken.ThrowIfCancellationRequested(); return new ManualFinancialBridgeReply( "manual-financial-selection-results", new { requestId, screen = ToWireValue(identity.Screen), stockName = identity.StockName, rowVersion = snapshot.RowVersion, result.BuilderKey, code = result.CutCode, aliases = new[] { result.CutCode }, selection = new { result.Selection.GroupCode, result.Selection.Subject, result.Selection.GraphicType, result.Selection.Subtype, result.Selection.DataCode }, result.CurrentPage, result.TotalPages }); }); } private async Task HandleManualFinancialCreateRequestAsync(JsonElement payload) { if (!TryParseManualFinancialCreateRequest( payload, out var requestId, out var record, out var stock)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "create", SafeManualFinancialScreen(payload), stockName: SafeManualFinancialRecordStockName(payload)); return; } InitializeManualFinancialRuntime(); await ExecuteManualFinancialWriteAsync( requestId, "create", record.Identity.Screen, record.Identity.StockName, async cancellationToken => { _ = await ResolveManualFinancialStockAsync( record.Identity, stock, cancellationToken); }, (service, cancellationToken) => service.CreateAsync(record, cancellationToken)); } private async Task HandleManualFinancialUpdateRequestAsync(JsonElement payload) { if (!TryParseManualFinancialUpdateRequest( payload, out var requestId, out var replacement, out var expectedRowVersion)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "update", SafeManualFinancialScreen(payload), stockName: SafeManualFinancialStockName(payload)); return; } InitializeManualFinancialRuntime(); ManualFinancialSnapshot? expected = null; await ExecuteManualFinancialWriteAsync( requestId, "update", replacement.Identity.Screen, replacement.Identity.StockName, async cancellationToken => { var service = RequireManualFinancialService(); expected = await service.GetAsync(replacement.Identity, cancellationToken); EnsureManualFinancialRowVersion(expected, expectedRowVersion); }, (service, cancellationToken) => service.UpdateAsync( expected ?? throw new InvalidOperationException("Update preflight did not complete."), replacement, cancellationToken)); } private async Task HandleManualFinancialDeleteRequestAsync(JsonElement payload) { if (!TryParseManualFinancialDeleteRequest( payload, out var requestId, out var identity, out var expectedRowVersion)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "delete-one", SafeManualFinancialScreen(payload), stockName: SafeManualFinancialStockName(payload)); return; } InitializeManualFinancialRuntime(); ManualFinancialSnapshot? expected = null; await ExecuteManualFinancialWriteAsync( requestId, "delete-one", identity.Screen, identity.StockName, async cancellationToken => { var service = RequireManualFinancialService(); expected = await service.GetAsync(identity, cancellationToken); EnsureManualFinancialRowVersion(expected, expectedRowVersion); }, (service, cancellationToken) => service.DeleteAsync( expected ?? throw new InvalidOperationException("Delete preflight did not complete."), cancellationToken)); } private async Task HandleManualFinancialDeleteAllRequestAsync(JsonElement payload) { if (!TryParseManualFinancialDeleteAllRequest( payload, out var requestId, out var screen)) { PostManualFinancialInvalidRequest( SafeManualFinancialRequestId(payload), "delete-all", SafeManualFinancialScreen(payload)); return; } InitializeManualFinancialRuntime(); await ExecuteManualFinancialWriteAsync( requestId, "delete-all", screen, string.Empty, preflight: null, (service, cancellationToken) => service.DeleteAllAsync(screen, cancellationToken)); } private async Task ExecuteManualFinancialReadAsync( string requestId, string operation, ManualFinancialScreenKind screen, string query, string stockName, Func> operationBody) { var requestCancellation = CancellationTokenSource.CreateLinkedTokenSource( _lifetimeCancellation.Token); var requestToken = requestCancellation.Token; var browserGeneration = Volatile.Read(ref _manualFinancialBrowserGeneration); var request = new ManualFinancialReadRequest( operation, requestId, screen, query, stockName, browserGeneration, requestCancellation); RegisterManualFinancialRead(request); var databaseActivityEntered = false; try { if (Volatile.Read(ref _playoutCommandInFlight) != 0) { requestCancellation.Cancel(); PostManualFinancialReadCancellationIfCurrent( request, "PLAYOUT_PRIORITY", "The read was canceled because playout has database priority."); return; } await _databaseActivityGate.WaitAsync(requestToken); databaseActivityEntered = true; requestToken.ThrowIfCancellationRequested(); if (Volatile.Read(ref _playoutCommandInFlight) != 0) { requestCancellation.Cancel(); PostManualFinancialReadCancellationIfCurrent( request, "PLAYOUT_PRIORITY", "The read was canceled because playout has database priority."); return; } if (browserGeneration != Volatile.Read(ref _manualFinancialBrowserGeneration)) { requestCancellation.Cancel(); return; } var reply = await operationBody(requestToken); requestToken.ThrowIfCancellationRequested(); if (browserGeneration != Volatile.Read(ref _manualFinancialBrowserGeneration) || !TryCompleteManualFinancialReadIfCurrent(request)) { return; } PostMessage(reply.MessageType, reply.Payload); } catch (OperationCanceledException) when (requestToken.IsCancellationRequested) { PostManualFinancialReadCancellationIfCurrent( request, "PLAYOUT_PRIORITY", "The read was canceled because playout has database priority."); } catch (DatabaseOperationException) when (requestToken.IsCancellationRequested) { PostManualFinancialReadCancellationIfCurrent( request, "PLAYOUT_PRIORITY", "The read was canceled because playout has database priority."); } catch (Exception) when (requestToken.IsCancellationRequested) { PostManualFinancialReadCancellationIfCurrent( request, "PLAYOUT_PRIORITY", "The read was canceled because playout has database priority."); } catch (ManualFinancialBridgeStaleException) { PostManualFinancialReadErrorIfCurrent( request, "STALE_ROW", "The manual-financial record changed. Reload before selecting it.", retryable: false); } catch (ManualFinancialNotFoundException) { PostManualFinancialReadErrorIfCurrent( request, "NOT_FOUND", "The manual-financial record does not exist.", retryable: false); } catch (ManualFinancialAmbiguousIdentityException) { PostManualFinancialReadErrorIfCurrent( request, "AMBIGUOUS_IDENTITY", "The manual-financial STOCK_NAME is duplicated.", retryable: false); } catch (ManualFinancialDataException) { PostManualFinancialReadErrorIfCurrent( request, "INVALID_DATA", "Stored manual-financial data does not match the migrated contract.", retryable: false); _ = PostDatabaseStatusAsync(_lifetimeCancellation.Token); } catch (StockSearchDataException) { PostManualFinancialReadErrorIfCurrent( request, "INVALID_STOCK_DATA", "The stock master result is ambiguous or invalid.", retryable: false); } catch (ArgumentException) { PostManualFinancialReadErrorIfCurrent( request, "INVALID_REQUEST", "The manual-financial request identity is invalid or ambiguous.", retryable: false); } catch (DatabaseOperationException exception) { PostManualFinancialReadErrorIfCurrent( request, "DATABASE_ERROR", exception.Message, retryable: true); _ = PostDatabaseStatusAsync(_lifetimeCancellation.Token); } catch { PostManualFinancialReadErrorIfCurrent( request, "DATABASE_ERROR", "The manual-financial database read failed.", retryable: true); _ = PostDatabaseStatusAsync(_lifetimeCancellation.Token); } finally { if (databaseActivityEntered) { _databaseActivityGate.Release(); } ReleaseManualFinancialRead(request); requestCancellation.Dispose(); } } private async Task ExecuteManualFinancialWriteAsync( string requestId, string operation, ManualFinancialScreenKind screen, string stockName, Func? preflight, Func> operationBody) { var service = _manualFinancialScreenService; if (service is null || !service.CanMutate) { PostManualFinancialUnavailable( requestId, operation, ToWireValue(screen), stockName: stockName); return; } if (IsManualFinancialWriteQuarantined()) { PostManualFinancialQuarantined(requestId, operation, screen, stockName); return; } if (!await _manualFinancialWriteGate.WaitAsync(0)) { PostManualFinancialMutationError( requestId, operation, screen, stockName, "WRITE_BUSY", "Another manual-financial write is already running.", outcomeUnknown: false); return; } var playoutGateEntered = false; var databaseActivityEntered = false; var mutationDispatched = false; var requestCancellation = CancellationTokenSource.CreateLinkedTokenSource( _lifetimeCancellation.Token); var requestToken = requestCancellation.Token; var browserGeneration = Volatile.Read(ref _manualFinancialBrowserGeneration); Volatile.Write(ref _manualFinancialWriteCancellation, requestCancellation); try { playoutGateEntered = await _playoutCommandGate.WaitAsync(0, requestToken); if (!playoutGateEntered || !CanStartManualFinancialWrite()) { PostManualFinancialPlayoutActive(requestId, operation, screen, stockName); return; } databaseActivityEntered = await _databaseActivityGate.WaitAsync(0, requestToken); if (!databaseActivityEntered) { PostManualFinancialMutationError( requestId, operation, screen, stockName, "DATABASE_BUSY", "The database is busy. Reload before making a new explicit write request.", outcomeUnknown: false); return; } requestToken.ThrowIfCancellationRequested(); if (!CanStartManualFinancialWrite()) { PostManualFinancialPlayoutActive(requestId, operation, screen, stockName); return; } if (browserGeneration != Volatile.Read(ref _manualFinancialBrowserGeneration)) { requestCancellation.Cancel(); return; } if (preflight is not null) { await preflight(requestToken); requestToken.ThrowIfCancellationRequested(); } if (!CanStartManualFinancialWrite() || browserGeneration != Volatile.Read(ref _manualFinancialBrowserGeneration)) { requestCancellation.Cancel(); return; } mutationDispatched = true; Interlocked.Exchange(ref _manualFinancialWriteInFlight, 1); var receipt = await operationBody(service, requestToken); if (browserGeneration != Volatile.Read(ref _manualFinancialBrowserGeneration) || !ReferenceEquals( Volatile.Read(ref _manualFinancialWriteCancellation), requestCancellation)) { LatchManualFinancialWriteQuarantine( ManualFinancialCorrelationQuarantineMessage); return; } PostMessage("manual-financial-mutation-result", new { requestId, operationId = receipt.OperationId.ToString("N", CultureInfo.InvariantCulture), operation, screen = ToWireValue(receipt.Screen), receipt.StockName, receipt.CommittedAt, receipt.AffectedRows }); } catch (OperationCanceledException) when (_lifetimeCancellation.IsCancellationRequested) { if (mutationDispatched) { LatchManualFinancialWriteQuarantine( ManualFinancialCorrelationQuarantineMessage); } } catch (ManualFinancialBridgeStaleException) { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "STALE_ROW", "The record changed. Reload before submitting a new explicit write.", outcomeUnknown: false, requestCancellation); } catch (ManualFinancialNotFoundException) { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "NOT_FOUND", "The manual-financial record no longer exists.", outcomeUnknown: false, requestCancellation); } catch (ManualFinancialAmbiguousIdentityException) { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "AMBIGUOUS_IDENTITY", "The manual-financial STOCK_NAME is duplicated. No write was attempted.", outcomeUnknown: false, requestCancellation); } catch (ManualFinancialMutationRejectedException exception) { var (code, message) = exception.Reason switch { ManualFinancialMutationRejectionReason.DuplicateIdentity => ("DUPLICATE_IDENTITY", "The stock already exists. Reload before editing it."), ManualFinancialMutationRejectionReason.NotFoundOrChanged => ("STALE_ROW", "The record changed or disappeared. Reload before editing it."), _ => ("AMBIGUOUS_IDENTITY", "The manual-financial identity is ambiguous.") }; PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, code, message, outcomeUnknown: false, requestCancellation); } catch (ManualFinancialMutationException exception) { if (exception.OutcomeUnknown) { LatchManualFinancialWriteQuarantine(exception.Message); PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "OUTCOME_UNKNOWN", ManualFinancialCorrelationQuarantineMessage, outcomeUnknown: true, requestCancellation); } else { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "WRITE_FAILED", "The Oracle transaction rolled back and was not retried.", outcomeUnknown: false, requestCancellation); } } catch (ArgumentException) { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "INVALID_REQUEST", "The manual-financial write identity or record is invalid.", outcomeUnknown: false, requestCancellation); } catch (StockSearchDataException) { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "INVALID_STOCK_DATA", "The stock master result is ambiguous or invalid.", outcomeUnknown: false, requestCancellation); } catch (OperationCanceledException) { if (mutationDispatched) { LatchManualFinancialWriteQuarantine( ManualFinancialCorrelationQuarantineMessage); PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "OUTCOME_UNKNOWN", ManualFinancialCorrelationQuarantineMessage, outcomeUnknown: true, requestCancellation); } else { PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, "CANCELLED", "The write stopped before an Oracle transaction began.", outcomeUnknown: false, requestCancellation); } } catch { if (mutationDispatched) { LatchManualFinancialWriteQuarantine( ManualFinancialCorrelationQuarantineMessage); } PostManualFinancialWriteErrorIfCurrent( requestId, operation, screen, stockName, mutationDispatched ? "OUTCOME_UNKNOWN" : "WRITE_FAILED", mutationDispatched ? ManualFinancialCorrelationQuarantineMessage : "The write failed before an Oracle transaction began.", outcomeUnknown: mutationDispatched, requestCancellation); } finally { Interlocked.Exchange(ref _manualFinancialWriteInFlight, 0); if (databaseActivityEntered) { _databaseActivityGate.Release(); } if (playoutGateEntered) { _playoutCommandGate.Release(); } Interlocked.CompareExchange( ref _manualFinancialWriteCancellation, null, requestCancellation); requestCancellation.Dispose(); _manualFinancialWriteGate.Release(); } } private bool CanStartManualFinancialWrite() { if (Volatile.Read(ref _playoutCommandInFlight) != 0 || Volatile.Read(ref _playoutShutdownStarted) != 0 || IsBrowserCorrelationQuarantined() || IsManualFinancialWriteQuarantined()) { return false; } return CanStartOperatorMutation(IsManualFinancialWriteQuarantined()); } private async Task ResolveManualFinancialStockAsync( ManualFinancialIdentity identity, ManualFinancialStockWire stock, CancellationToken cancellationToken) { var stockService = _stockSearchService ?? throw new InvalidOperationException( "The domestic stock-search service is unavailable."); if (!string.Equals(identity.StockName, stock.Name, StringComparison.Ordinal)) { throw new ArgumentException("The supplied stock name does not match the record."); } var search = await stockService.SearchAsync( identity.StockName, LegacyStockSearchService.MaximumResults, cancellationToken); var verified = ManualFinancialCutContracts.VerifyStock( identity, search.Items, stock.Market, stock.Code); if (verified.Source != stock.Source || !string.Equals(verified.Name, stock.Name, StringComparison.Ordinal)) { throw new ArgumentException("The supplied stock provider identity is invalid."); } return verified; } private IManualFinancialScreenService RequireManualFinancialService() => _manualFinancialScreenService ?? throw new InvalidOperationException( "The manual-financial service is unavailable."); private static void EnsureManualFinancialRowVersion( ManualFinancialSnapshot snapshot, string expectedRowVersion) { if (!string.Equals(snapshot.RowVersion, expectedRowVersion, StringComparison.Ordinal)) { throw new ManualFinancialBridgeStaleException(); } } private void RegisterManualFinancialRead(ManualFinancialReadRequest request) { ManualFinancialReadRequest? superseded; var shouldPostSuperseded = false; lock (_manualFinancialReadLanesGate) { _manualFinancialReadLanes.TryGetValue(request.Lane, out superseded); _manualFinancialReadLanes[request.Lane] = request; shouldPostSuperseded = superseded?.TryComplete() == true; } if (superseded is null) { return; } CancelRequest(superseded.Cancellation); if (shouldPostSuperseded) { PostManualFinancialReadError( superseded.RequestId, superseded.Operation, superseded.Screen, superseded.Query, superseded.StockName, "CANCELLED", "The read was canceled because a newer request replaced it.", retryable: true); } } private bool TryCompleteManualFinancialReadIfCurrent(ManualFinancialReadRequest request) { lock (_manualFinancialReadLanesGate) { return request.BrowserGeneration == Volatile.Read(ref _manualFinancialBrowserGeneration) && _manualFinancialReadLanes.TryGetValue(request.Lane, out var current) && ReferenceEquals(current, request) && request.TryComplete(); } } private void ReleaseManualFinancialRead(ManualFinancialReadRequest request) { lock (_manualFinancialReadLanesGate) { if (_manualFinancialReadLanes.TryGetValue(request.Lane, out var current) && ReferenceEquals(current, request)) { _manualFinancialReadLanes.Remove(request.Lane); } } } private ManualFinancialReadRequest[] SnapshotManualFinancialReads(bool clear) { lock (_manualFinancialReadLanesGate) { var requests = _manualFinancialReadLanes.Values.ToArray(); if (clear) { _manualFinancialReadLanes.Clear(); foreach (var request in requests) { _ = request.TryComplete(); } } return requests; } } private void PostManualFinancialReadCancellationIfCurrent( ManualFinancialReadRequest request, string code, string message) => PostManualFinancialReadErrorIfCurrent( request, code, message, retryable: true); private void PostManualFinancialReadErrorIfCurrent( ManualFinancialReadRequest request, string code, string message, bool retryable) { if (_lifetimeCancellation.IsCancellationRequested || !TryCompleteManualFinancialReadIfCurrent(request)) { return; } PostManualFinancialReadError( request.RequestId, request.Operation, request.Screen, request.Query, request.StockName, code, message, retryable); } private void PostManualFinancialWriteErrorIfCurrent( string requestId, string operation, ManualFinancialScreenKind screen, string stockName, string code, string message, bool outcomeUnknown, CancellationTokenSource requestCancellation) { if (_lifetimeCancellation.IsCancellationRequested || !ReferenceEquals( Volatile.Read(ref _manualFinancialWriteCancellation), requestCancellation)) { return; } PostManualFinancialMutationError( requestId, operation, screen, stockName, code, message, outcomeUnknown); } private void PostManualFinancialReadError( string requestId, string operation, ManualFinancialScreenKind screen, string query, string stockName, string code, string message, bool retryable) { if (operation == "list") { PostMessage("manual-financial-list-error", new { requestId, screen = ToWireValue(screen), query, message }); return; } PostMessage( operation == "selection" ? "manual-financial-selection-error" : "manual-financial-load-error", new { requestId, screen = ToWireValue(screen), stockName, code, message, retryable }); } private void PostManualFinancialInvalidRequest( string requestId, string operation, string screen, string query = "", string stockName = "") { if (TryParseManualFinancialScreen(screen, out var parsedScreen)) { if (operation is "list" or "load" or "selection") { PostManualFinancialReadError( requestId, operation, parsedScreen, query, stockName, "INVALID_REQUEST", "The manual-financial request payload is invalid.", retryable: false); return; } PostManualFinancialMutationError( requestId, operation, parsedScreen, stockName, "INVALID_REQUEST", "The manual-financial write payload is invalid.", outcomeUnknown: false); return; } PostMessage("manual-financial-request-error", new { requestId, operation, code = "INVALID_REQUEST", message = "The manual-financial request payload is invalid." }); } private void PostManualFinancialUnavailable( string requestId, string operation, string screen, string query = "", string stockName = "") { if (TryParseManualFinancialScreen(screen, out var parsedScreen) && operation is "list" or "load" or "selection") { PostManualFinancialReadError( requestId, operation, parsedScreen, query, stockName, "DATABASE_UNAVAILABLE", _manualFinancialInitializationError ?? _databaseInitializationError ?? "The manual-financial database boundary is unavailable.", retryable: false); return; } if (TryParseManualFinancialScreen(screen, out parsedScreen)) { PostManualFinancialMutationError( requestId, operation, parsedScreen, stockName, "DATABASE_UNAVAILABLE", _manualFinancialInitializationError ?? _databaseInitializationError ?? "The manual-financial database boundary is unavailable.", outcomeUnknown: false); } } private void PostManualFinancialPlayoutActive( string requestId, string operation, ManualFinancialScreenKind screen, string stockName) => PostManualFinancialMutationError( requestId, operation, screen, stockName, "PLAYOUT_ACTIVE", "Manual-financial writes are allowed only while playout is fully IDLE.", outcomeUnknown: false); private void PostManualFinancialQuarantined( string requestId, string operation, ManualFinancialScreenKind screen, string stockName) => PostManualFinancialMutationError( requestId, operation, screen, stockName, "OUTCOME_UNKNOWN", GetOperatorMutationQuarantineMessage( Volatile.Read(ref _manualFinancialWriteQuarantineMessage) ?? ManualFinancialCorrelationQuarantineMessage), outcomeUnknown: true); private void PostManualFinancialMutationError( string requestId, string operation, ManualFinancialScreenKind screen, string stockName, string code, string message, bool outcomeUnknown) { PostMessage("manual-financial-mutation-error", new { requestId, operation, screen = ToWireValue(screen), stockName, code, message, retryable = false, outcomeUnknown }); } private bool IsManualFinancialWriteQuarantined() => Volatile.Read(ref _manualFinancialWriteQuarantined) != 0 || IsOperatorMutationQuarantined(); private void LatchManualFinancialWriteQuarantine(string message) { LatchOperatorMutationQuarantine(message); if (Interlocked.CompareExchange(ref _manualFinancialWriteQuarantined, 1, 0) == 0) { Volatile.Write( ref _manualFinancialWriteQuarantineMessage, string.IsNullOrWhiteSpace(message) ? ManualFinancialCorrelationQuarantineMessage : message); } } /// /// Root browser-navigation/process-failure hook. Correlation loss during a /// dispatched write permanently quarantines writes for this process. /// private void InvalidateManualFinancialBrowserRequests() { Interlocked.Increment(ref _manualFinancialBrowserGeneration); if (Volatile.Read(ref _manualFinancialWriteInFlight) != 0) { LatchManualFinancialWriteQuarantine( ManualFinancialCorrelationQuarantineMessage); } foreach (var request in SnapshotManualFinancialReads(clear: true)) { CancelRequest(request.Cancellation); } CancelRequest(Interlocked.Exchange(ref _manualFinancialWriteCancellation, null)); } /// Root playout-priority hook; writes own the playout gate. private void CancelManualFinancialReadsForPlayout() { foreach (var request in SnapshotManualFinancialReads(clear: false)) { CancelRequest(request.Cancellation); } } /// Root window-close hook. private void ShutdownManualFinancialRuntime() { if (Volatile.Read(ref _manualFinancialWriteInFlight) != 0) { LatchManualFinancialWriteQuarantine( ManualFinancialCorrelationQuarantineMessage); } foreach (var request in SnapshotManualFinancialReads(clear: true)) { CancelRequest(request.Cancellation); } CancelRequest(Interlocked.Exchange(ref _manualFinancialWriteCancellation, null)); _manualFinancialScreenService = null; } private static bool TryParseManualFinancialListRequest( JsonElement payload, out string requestId, out ManualFinancialScreenKind screen, out string query, out int maximumResults) { requestId = string.Empty; screen = default; query = string.Empty; maximumResults = LegacyManualFinancialScreenService.DefaultMaximumResults; return payload.ValueKind == JsonValueKind.Object && HasOnlyProperties(payload, "requestId", "screen", "query", "maximumResults") && TryGetSafeToken(payload, "requestId", MaximumPlayoutRequestIdLength, out requestId) && TryParseManualFinancialScreen(GetString(payload, "screen"), out screen) && TryGetManualFinancialQuery(payload, "query", out query) && TryGetOptionalManualFinancialLimit(payload, ref maximumResults); } private static bool TryParseManualFinancialIdentityRequest( JsonElement payload, string[] exactProperties, out string requestId, out ManualFinancialIdentity identity) { requestId = string.Empty; identity = new ManualFinancialIdentity(default, string.Empty); if (payload.ValueKind != JsonValueKind.Object || !HasOnlyProperties(payload, exactProperties) || !TryGetSafeToken(payload, "requestId", MaximumPlayoutRequestIdLength, out requestId) || !TryParseManualFinancialScreen(GetString(payload, "screen"), out var screen) || !TryGetManualFinancialText( payload, "stockName", 200, allowEmpty: false, allowUnderscore: true, out var stockName)) { return false; } identity = new ManualFinancialIdentity(screen, stockName); return true; } private static bool TryParseManualFinancialCreateRequest( JsonElement payload, out string requestId, out ManualFinancialRecord record, out ManualFinancialStockWire stock) { requestId = string.Empty; record = null!; stock = default; return payload.ValueKind == JsonValueKind.Object && HasOnlyProperties(payload, "requestId", "screen", "stock", "record") && TryGetSafeToken(payload, "requestId", MaximumPlayoutRequestIdLength, out requestId) && TryParseManualFinancialScreen(GetString(payload, "screen"), out var screen) && payload.TryGetProperty("record", out var recordElement) && TryParseManualFinancialRecord(recordElement, screen, out record) && payload.TryGetProperty("stock", out var stockElement) && TryParseManualFinancialStock(stockElement, record.Identity.StockName, out stock); } private static bool TryParseManualFinancialUpdateRequest( JsonElement payload, out string requestId, out ManualFinancialRecord replacement, out string expectedRowVersion) { requestId = string.Empty; replacement = null!; expectedRowVersion = string.Empty; if (payload.ValueKind != JsonValueKind.Object || !HasOnlyProperties( payload, "requestId", "screen", "stockName", "expectedRowVersion", "record") || !TryGetSafeToken(payload, "requestId", MaximumPlayoutRequestIdLength, out requestId) || !TryParseManualFinancialScreen(GetString(payload, "screen"), out var screen) || !TryGetManualFinancialText( payload, "stockName", 200, allowEmpty: false, allowUnderscore: true, out var stockName) || !TryGetManualFinancialRowVersion(payload, "expectedRowVersion", out expectedRowVersion) || !payload.TryGetProperty("record", out var recordElement) || !TryParseManualFinancialRecord(recordElement, screen, out replacement)) { return false; } return string.Equals(replacement.Identity.StockName, stockName, StringComparison.Ordinal); } private static bool TryParseManualFinancialDeleteRequest( JsonElement payload, out string requestId, out ManualFinancialIdentity identity, out string expectedRowVersion) { expectedRowVersion = string.Empty; return TryParseManualFinancialIdentityRequest( payload, ["requestId", "screen", "stockName", "expectedRowVersion"], out requestId, out identity) && TryGetManualFinancialRowVersion( payload, "expectedRowVersion", out expectedRowVersion); } private static bool TryParseManualFinancialDeleteAllRequest( JsonElement payload, out string requestId, out ManualFinancialScreenKind screen) { requestId = string.Empty; screen = default; if (payload.ValueKind != JsonValueKind.Object || !HasOnlyProperties(payload, "requestId", "screen", "confirmationToken") || !TryGetSafeToken(payload, "requestId", MaximumPlayoutRequestIdLength, out requestId) || !TryParseManualFinancialScreen(GetString(payload, "screen"), out screen)) { return false; } var expected = "DELETE_ALL_" + ManualFinancialCutContracts.Get(screen).TableName; return string.Equals( GetString(payload, "confirmationToken"), expected, StringComparison.Ordinal); } private static bool TryParseManualFinancialSelectionRequest( JsonElement payload, out string requestId, out ManualFinancialIdentity identity, out string rowVersion, out ManualFinancialStockWire stock) { rowVersion = string.Empty; stock = default; if (!TryParseManualFinancialIdentityRequest( payload, ["requestId", "screen", "stockName", "rowVersion", "stock"], out requestId, out identity) || !TryGetManualFinancialRowVersion(payload, "rowVersion", out rowVersion) || !payload.TryGetProperty("stock", out var stockElement) || !TryParseManualFinancialStock(stockElement, identity.StockName, out stock)) { return false; } return true; } private static bool TryParseManualFinancialRecord( JsonElement element, ManualFinancialScreenKind screen, out ManualFinancialRecord record) { record = null!; if (element.ValueKind != JsonValueKind.Object || !TryGetManualFinancialText( element, "kind", 32, allowEmpty: false, allowUnderscore: false, out var kind) || !string.Equals(kind, ToWireValue(screen), StringComparison.Ordinal) || !TryGetManualFinancialText( element, "stockName", 200, allowEmpty: false, allowUnderscore: true, out var stockName)) { return false; } var identity = new ManualFinancialIdentity(screen, stockName); switch (screen) { case ManualFinancialScreenKind.RevenueComposition: if (!HasOnlyProperties(element, "kind", "stockName", "baseDate", "slices") || !TryGetManualFinancialText( element, "baseDate", 200, allowEmpty: true, allowUnderscore: true, out var baseDate) || !TryParseManualRevenueSlices(element, out var slices)) { return false; } record = new ManualRevenueCompositionRecord(identity, baseDate, slices); return true; case ManualFinancialScreenKind.GrowthMetrics: if (!HasOnlyProperties( element, "kind", "stockName", "salesGrowth", "operatingProfitGrowth", "netAssetGrowth", "netIncomeGrowth", "periods") || !TryParseManualGrowthSeries(element, "salesGrowth", out var salesGrowth) || !TryParseManualGrowthSeries( element, "operatingProfitGrowth", out var operatingProfitGrowth) || !TryParseManualGrowthSeries(element, "netAssetGrowth", out var netAssetGrowth) || !TryParseManualGrowthSeries(element, "netIncomeGrowth", out var netIncomeGrowth) || !TryParseManualPeriods(element, out var periods)) { return false; } record = new ManualGrowthMetricsRecord( identity, salesGrowth, operatingProfitGrowth, netAssetGrowth, netIncomeGrowth, periods); return true; case ManualFinancialScreenKind.Sales: case ManualFinancialScreenKind.OperatingProfit: if (!HasOnlyProperties(element, "kind", "stockName", "quarters") || !TryParseManualQuarters(element, out var quarters)) { return false; } record = screen == ManualFinancialScreenKind.Sales ? new ManualSalesRecord(identity, quarters) : new ManualOperatingProfitRecord(identity, quarters); return true; default: return false; } } private static bool TryParseManualRevenueSlices( JsonElement record, out IReadOnlyList slices) { slices = Array.Empty(); if (!record.TryGetProperty("slices", out var element) || element.ValueKind != JsonValueKind.Array || element.GetArrayLength() != 5) { return false; } var result = new List(5); foreach (var slice in element.EnumerateArray()) { if (slice.ValueKind == JsonValueKind.Null) { result.Add(null); continue; } if (slice.ValueKind != JsonValueKind.Object || !HasOnlyProperties(slice, "label", "percentageText", "percentage") || !TryGetManualFinancialText( slice, "label", 200, allowEmpty: false, allowUnderscore: false, out var label) || !TryGetManualFinancialText( slice, "percentageText", 64, allowEmpty: false, allowUnderscore: false, out var percentageText) || !slice.TryGetProperty("percentage", out var percentageElement) || percentageElement.ValueKind != JsonValueKind.Number || !percentageElement.TryGetDouble(out var percentage) || !double.IsFinite(percentage)) { return false; } result.Add(new ManualRevenueSlice(label, percentageText, percentage)); } slices = result; return true; } private static bool TryParseManualGrowthSeries( JsonElement record, string propertyName, out ManualGrowthSeriesValues values) { values = new ManualGrowthSeriesValues(null, null, null, null); if (!record.TryGetProperty(propertyName, out var element) || element.ValueKind != JsonValueKind.Array || element.GetArrayLength() != 4) { return false; } var parsed = new double?[4]; var index = 0; foreach (var value in element.EnumerateArray()) { if (value.ValueKind == JsonValueKind.Null) { parsed[index++] = null; continue; } if (value.ValueKind != JsonValueKind.Number || !value.TryGetDouble(out var number) || !double.IsFinite(number)) { return false; } parsed[index++] = number; } values = new ManualGrowthSeriesValues(parsed[0], parsed[1], parsed[2], parsed[3]); return true; } private static bool TryParseManualPeriods( JsonElement record, out ManualGrowthPeriodLabels periods) { periods = new ManualGrowthPeriodLabels(string.Empty, string.Empty, string.Empty, string.Empty); if (!record.TryGetProperty("periods", out var element) || element.ValueKind != JsonValueKind.Array || element.GetArrayLength() != 4) { return false; } var parsed = new string[4]; var index = 0; foreach (var value in element.EnumerateArray()) { if (value.ValueKind != JsonValueKind.String || !IsSafeManualFinancialText( value.GetString(), 200, allowEmpty: true, allowUnderscore: false, out parsed[index++])) { return false; } } periods = new ManualGrowthPeriodLabels(parsed[0], parsed[1], parsed[2], parsed[3]); return true; } private static bool TryParseManualQuarters( JsonElement record, out IReadOnlyList quarters) { quarters = Array.Empty(); if (!record.TryGetProperty("quarters", out var element) || element.ValueKind != JsonValueKind.Array || element.GetArrayLength() != 6) { return false; } var result = new List(6); foreach (var item in element.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object || !HasOnlyProperties(item, "quarter", "value") || !TryGetManualFinancialText( item, "quarter", 200, allowEmpty: false, allowUnderscore: false, out var quarter) || !item.TryGetProperty("value", out var valueElement) || valueElement.ValueKind != JsonValueKind.Number || !valueElement.TryGetInt32(out var value)) { return false; } result.Add(new ManualQuarterValue(quarter, value)); } quarters = result; return true; } private static bool TryParseManualFinancialStock( JsonElement element, string expectedName, out ManualFinancialStockWire stock) { stock = default; if (element.ValueKind != JsonValueKind.Object || !HasOnlyProperties(element, "market", "source", "name", "code") || !TryParseStockMarket(GetString(element, "market"), out var market) || !TryParseDataSource(GetString(element, "source"), out var source) || !TryGetManualFinancialText( element, "name", 200, allowEmpty: false, allowUnderscore: true, out var name) || !TryGetManualFinancialStockCode(element, "code", out var code) || !string.Equals(name, expectedName, StringComparison.Ordinal) || (market is StockMarket.Kospi or StockMarket.Kosdaq ? source != DataSourceKind.Oracle : source != DataSourceKind.MariaDb)) { return false; } stock = new ManualFinancialStockWire(market, source, name, code); return true; } private static bool TryGetOptionalManualFinancialLimit( JsonElement payload, ref int maximumResults) { if (!payload.TryGetProperty("maximumResults", out var element)) { return true; } return element.ValueKind == JsonValueKind.Number && element.TryGetInt32(out maximumResults) && maximumResults is >= 1 and <= LegacyManualFinancialScreenService.MaximumResults; } private static bool TryGetManualFinancialQuery( JsonElement payload, string propertyName, out string query) { query = string.Empty; if (!payload.TryGetProperty(propertyName, out var element) || element.ValueKind != JsonValueKind.String) { return false; } var value = element.GetString(); return value is not null && value.Length <= LegacyManualFinancialScreenService.MaximumQueryLength && string.Equals(value, value.Trim(), StringComparison.Ordinal) && IsSafeManualFinancialText( value, LegacyManualFinancialScreenService.MaximumQueryLength, allowEmpty: true, allowUnderscore: true, out query); } private static bool TryGetManualFinancialText( JsonElement payload, string propertyName, int maximumLength, bool allowEmpty, bool allowUnderscore, out string value) { value = string.Empty; return payload.TryGetProperty(propertyName, out var element) && element.ValueKind == JsonValueKind.String && IsSafeManualFinancialText( element.GetString(), maximumLength, allowEmpty, allowUnderscore, out value); } private static bool IsSafeManualFinancialText( string? input, int maximumLength, bool allowEmpty, bool allowUnderscore, out string value) { value = input ?? string.Empty; if (input is null || (!allowEmpty && input.Length == 0) || input.Length > maximumLength || input != input.Trim() || input.Contains('^') || (!allowUnderscore && input.Contains('_'))) { return false; } foreach (var character in input) { var category = CharUnicodeInfo.GetUnicodeCategory(character); if (char.IsControl(character) || char.IsSurrogate(character) || character == '\uFFFD' || category is UnicodeCategory.Format or UnicodeCategory.LineSeparator or UnicodeCategory.ParagraphSeparator) { return false; } } return true; } private static bool TryGetManualFinancialRowVersion( JsonElement payload, string propertyName, out string value) { value = GetString(payload, propertyName) ?? string.Empty; return value.Length == 64 && value.All(static character => char.IsAsciiDigit(character) || character is >= 'A' and <= 'F'); } private static bool TryGetManualFinancialStockCode( JsonElement payload, string propertyName, out string value) { value = GetString(payload, propertyName) ?? string.Empty; return value.Length is >= 1 and <= 32 && value.All(char.IsAsciiLetterOrDigit); } private static bool TryParseManualFinancialScreen( string? value, out ManualFinancialScreenKind screen) { screen = value switch { "revenue-composition" => ManualFinancialScreenKind.RevenueComposition, "growth-metrics" => ManualFinancialScreenKind.GrowthMetrics, "sales" => ManualFinancialScreenKind.Sales, "operating-profit" => ManualFinancialScreenKind.OperatingProfit, _ => default }; return value is "revenue-composition" or "growth-metrics" or "sales" or "operating-profit"; } private static string ToWireValue(ManualFinancialScreenKind screen) => screen switch { ManualFinancialScreenKind.RevenueComposition => "revenue-composition", ManualFinancialScreenKind.GrowthMetrics => "growth-metrics", ManualFinancialScreenKind.Sales => "sales", ManualFinancialScreenKind.OperatingProfit => "operating-profit", _ => throw new ArgumentOutOfRangeException(nameof(screen)) }; private static bool TryParseStockMarket(string? value, out StockMarket market) { market = value switch { "kospi" => StockMarket.Kospi, "kosdaq" => StockMarket.Kosdaq, "nxt-kospi" => StockMarket.NxtKospi, "nxt-kosdaq" => StockMarket.NxtKosdaq, _ => default }; return value is "kospi" or "kosdaq" or "nxt-kospi" or "nxt-kosdaq"; } private static bool TryParseDataSource(string? value, out DataSourceKind source) { source = value switch { "oracle" => DataSourceKind.Oracle, "mariaDb" => DataSourceKind.MariaDb, _ => default }; return value is "oracle" or "mariaDb"; } private static object CreateManualFinancialSnapshotPayload(ManualFinancialSnapshot snapshot) => new { stockName = snapshot.Record.Identity.StockName, snapshot.RowVersion, record = CreateManualFinancialRecordPayload(snapshot.Record) }; private static object CreateManualFinancialRecordPayload(ManualFinancialRecord record) => record switch { ManualRevenueCompositionRecord revenue => new { kind = ToWireValue(revenue.Identity.Screen), stockName = revenue.Identity.StockName, revenue.BaseDate, slices = revenue.Slices.Select(slice => slice is null ? null : (object)new { slice.Label, slice.PercentageText, slice.Percentage }) }, ManualGrowthMetricsRecord growth => new { kind = ToWireValue(growth.Identity.Screen), stockName = growth.Identity.StockName, salesGrowth = growth.SalesGrowth.ToArray(), operatingProfitGrowth = growth.OperatingProfitGrowth.ToArray(), netAssetGrowth = growth.NetAssetGrowth.ToArray(), netIncomeGrowth = growth.NetIncomeGrowth.ToArray(), periods = growth.Periods.ToArray() }, ManualSalesRecord sales => new { kind = ToWireValue(sales.Identity.Screen), stockName = sales.Identity.StockName, quarters = sales.Quarters.Select(quarter => new { quarter.Quarter, quarter.Value }) }, ManualOperatingProfitRecord profit => new { kind = ToWireValue(profit.Identity.Screen), stockName = profit.Identity.StockName, quarters = profit.Quarters.Select(quarter => new { quarter.Quarter, quarter.Value }) }, _ => throw new ArgumentOutOfRangeException(nameof(record)) }; private static string SafeManualFinancialRequestId(JsonElement payload) => payload.ValueKind == JsonValueKind.Object && TryGetSafeToken(payload, "requestId", MaximumPlayoutRequestIdLength, out var requestId) ? requestId : string.Empty; private static string SafeManualFinancialScreen(JsonElement payload) { var value = payload.ValueKind == JsonValueKind.Object ? GetString(payload, "screen") : null; return TryParseManualFinancialScreen(value, out var screen) ? ToWireValue(screen) : string.Empty; } private static string SafeManualFinancialQuery(JsonElement payload) => payload.ValueKind == JsonValueKind.Object && TryGetManualFinancialQuery(payload, "query", out var query) ? query : string.Empty; private static string SafeManualFinancialStockName(JsonElement payload) => payload.ValueKind == JsonValueKind.Object && TryGetManualFinancialText( payload, "stockName", 200, allowEmpty: false, allowUnderscore: true, out var stockName) ? stockName : string.Empty; private static string SafeManualFinancialRecordStockName(JsonElement payload) { if (payload.ValueKind != JsonValueKind.Object || !payload.TryGetProperty("record", out var record) || record.ValueKind != JsonValueKind.Object) { return string.Empty; } return SafeManualFinancialStockName(record); } private readonly record struct ManualFinancialBridgeReply(string MessageType, object Payload); private sealed class ManualFinancialReadRequest { private int _terminalState; public ManualFinancialReadRequest( string lane, string requestId, ManualFinancialScreenKind screen, string query, string stockName, int browserGeneration, CancellationTokenSource cancellation) { Lane = lane; RequestId = requestId; Operation = lane; Screen = screen; Query = query; StockName = stockName; BrowserGeneration = browserGeneration; Cancellation = cancellation; } public string Lane { get; } public string RequestId { get; } public string Operation { get; } public ManualFinancialScreenKind Screen { get; } public string Query { get; } public string StockName { get; } public int BrowserGeneration { get; } public CancellationTokenSource Cancellation { get; } public bool TryComplete() => Interlocked.CompareExchange(ref _terminalState, 1, 0) == 0; } private readonly record struct ManualFinancialStockWire( StockMarket Market, DataSourceKind Source, string Name, string Code); private sealed class ManualFinancialBridgeStaleException : Exception; }