From 7fc6b4ba922005f87eac522b3718d943c353c719 Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 13:09:45 -0800 Subject: [PATCH 01/13] fix(954): IPC ghost conn stub, RETEST null state leak, deprecated mode fallback Fix 1 (V12_001.cs): Stub ConnectToStrategy() with early return -- strategy no longer hosts IPC server after Phase 6 pruning. Panel runs in standalone UI mode; SendCommand calls safely no-op via tcpStream null guard. Fix 2 (V12_002.Entries.Retest.cs): Insert return; inside null-order guard after SubmitOrderUnmanaged fails. Prevents retestFiredThisSession latch and SIMA dispatch from arming on a failed order submission. Fix 3 (V12_001.cs): Add else branch in LoadConfig() to normalize both activeMode and selectedConfigMode to RMA when a deprecated/pruned mode is found in saved config. Prevents GetSettings() from loading garbage data from a deleted slot. Build tag incremented to 954. Co-Authored-By: Claude Sonnet 4.6 --- src/V12_001.cs | 17 ++++++++++++++++- src/V12_002.Entries.Retest.cs | 1 + src/V12_002.cs | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/V12_001.cs b/src/V12_001.cs index 3fb09247..2fb036c7 100644 --- a/src/V12_001.cs +++ b/src/V12_001.cs @@ -785,7 +785,19 @@ private void LoadConfig() selectedTargetCount = activeCount; Button modeBtn = GetModeButton(activeMode); - if (modeBtn != null) HighlightModeButton(modeBtn); + if (modeBtn != null) + { + HighlightModeButton(modeBtn); + } + else + { + // [Build 954]: Saved mode is deprecated/unrecognized -- normalize both vars to RMA baseline. + Print("[WARN][954] Unrecognized saved mode '" + activeMode + "' -- falling back to RMA."); + activeMode = "RMA"; + selectedConfigMode = "RMA"; + modeBtn = GetModeButton("RMA"); + if (modeBtn != null) HighlightModeButton(modeBtn); + } // Apply active mode+count settings to UI ApplySettings(fullConfig.GetSettings(activeMode, activeCount)); @@ -2986,6 +2998,9 @@ private void SyncAll_Click(object sender, RoutedEventArgs e) private void ConnectToStrategy() { + // [Build 954]: IPC deprecated -- strategy no longer hosts IPC server (Phase 6 pruning). + // Panel operates in standalone UI mode. All SendCommand calls are safely no-oped via tcpStream null guard. + return; try { lock (tcpLock) diff --git a/src/V12_002.Entries.Retest.cs b/src/V12_002.Entries.Retest.cs index e14f9d31..2d4e6646 100644 --- a/src/V12_002.Entries.Retest.cs +++ b/src/V12_002.Entries.Retest.cs @@ -186,6 +186,7 @@ private void ExecuteRetestEntry(int contracts) { AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRetest); Print("[ERROR][1102Y-V3] RETEST SubmitOrderUnmanaged NULL for " + entryName + " -- rolled back."); + return; // [Build 954]: Do not latch session or dispatch SIMA for a failed order. } entryOrders[entryName] = entryOrder; diff --git a/src/V12_002.cs b/src/V12_002.cs index f78fdc7c..e9ecbd84 100644 --- a/src/V12_002.cs +++ b/src/V12_002.cs @@ -41,7 +41,7 @@ namespace NinjaTrader.NinjaScript.Strategies { public partial class V12_002 : Strategy { - public const string BUILD_TAG = "950"; // V12.950: OCO Cascade Fix (bracket restore on V8.30 timeout path) + public const string BUILD_TAG = "954"; // V12.954: Bot Audit Fixes (IPC ghost conn, RETEST null state, deprecated mode fallback) #region Variables From 2c337a732fcd474125f332fe643bd9cb4f968b4b Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 14:18:46 -0800 Subject: [PATCH 02/13] fix(955): Race condition & TOCTOU remediation -- PR #24 audit fixes FINDING 1 (CRITICAL) -- V12_002.Trailing.cs: - Snapshot CapturedTargets/BracketRestorationNeeded BEFORE pendingStopReplacements.TryAdd in both TryAdd paths (CancelPending/Submitted and Working/Accepted state handlers). - Eliminated race window where stop-cancel callback could read a partially-constructed PendingStopReplacement with BracketRestorationNeeded=false and skip RestoreCascadedTargets. - Removed now-redundant post-TryAdd snapshot blocks (were dead/duplicate after fix). FINDING 2 (HIGH) -- V12_002.Orders.Callbacks.cs: - HandleOrderCancelled (master path): snapshot RemainingContracts under stateLock once; use same snapshot for guard check and CreateNewStopOrder call. Eliminates 0-qty stop risk. - OnAccountOrderUpdate (follower path): move RemainingContracts guard inside stateLock so check and use are atomic. Eliminates TOCTOU on follower stop replacement qty. FINDING 3 (LOW) -- V12_001.cs: - Remove unreachable try/catch body after intentional return stub in ConnectToStrategy(). SendCommand() safely no-ops via tcpStream null guard -- no behavior change. Bump BUILD_TAG -> 955. ASCII gate: PASS. Co-Authored-By: Claude Sonnet 4.6 --- src/V12_001.cs | 66 +--------------------------- src/V12_002.Orders.Callbacks.cs | 27 +++++++----- src/V12_002.Trailing.cs | 78 +++++++++++++++------------------ src/V12_002.cs | 2 +- 4 files changed, 56 insertions(+), 117 deletions(-) diff --git a/src/V12_001.cs b/src/V12_001.cs index 2fb036c7..60402735 100644 --- a/src/V12_001.cs +++ b/src/V12_001.cs @@ -2998,70 +2998,8 @@ private void SyncAll_Click(object sender, RoutedEventArgs e) private void ConnectToStrategy() { - // [Build 954]: IPC deprecated -- strategy no longer hosts IPC server (Phase 6 pruning). - // Panel operates in standalone UI mode. All SendCommand calls are safely no-oped via tcpStream null guard. - return; - try - { - lock (tcpLock) - { - if (isConnected) return; - - tcpClient = new TcpClient(); - tcpClient.Connect("127.0.0.1", IpcPort); - tcpStream = tcpClient.GetStream(); - isConnected = true; - // [Build 934]: Reset retry counters on successful connect - _ipcRetryCount = 0; - _lastRetryLogTime = DateTime.MinValue; - - Print($"V12 Panel: Strategy connected on port {IpcPort} ?"); - - if (ChartControl != null) - { - ChartControl.Dispatcher.BeginInvoke(new Action(() => - { - if (hubStatusLed != null) - { - hubStatusLed.Background = GreenFg; - hubStatusLed.ToolTip = "IPC Connected"; - } - })); - } - - receiveThread = new Thread(ReceiveLoop) { IsBackground = true, Name = "V12_Std_Receive" }; - receiveThread.Start(); - - SendCommand("GET_LAYOUT"); - } - } - catch (Exception) - { - isConnected = false; - _ipcRetryCount++; - - // [Build 934]: Log only on first failure and then at most once per 60 seconds - if (_ipcRetryCount == 1 || (DateTime.Now - _lastRetryLogTime).TotalSeconds >= 60) - { - Print($"V12 Panel: Strategy offline -- retrying in background (attempt #{_ipcRetryCount})"); - _lastRetryLogTime = DateTime.Now; - } - - if (ChartControl != null) - { - ChartControl.Dispatcher.BeginInvoke(new Action(() => - { - if (hubStatusLed != null) - { - hubStatusLed.Background = TextMuted; - hubStatusLed.ToolTip = "Waiting for Strategy (retrying...)"; - } - })); - } - - // [Build 933]: Start retry loop on initial failure (market closed / Strategy not yet live). - ScheduleReconnect(); - } + // [Build 954]: IPC deprecated. Strategy no longer hosts IPC server (Phase 6 pruning). + // [Build 955]: Dead code removed. SendCommand() is safely no-oped via tcpStream null guard. } private void DisconnectFromStrategy() diff --git a/src/V12_002.Orders.Callbacks.cs b/src/V12_002.Orders.Callbacks.cs index d76699ff..f540055e 100644 --- a/src/V12_002.Orders.Callbacks.cs +++ b/src/V12_002.Orders.Callbacks.cs @@ -369,9 +369,12 @@ private bool HandleOrderCancelled(Order order) { if (kvp.Value.OldOrder == order && activePositions.TryGetValue(kvp.Key, out var pos)) { - if (pos.RemainingContracts > 0) + // Build 955: Snapshot qty under stateLock -- single atomic read for both check and use. + int _stopQty; + lock (stateLock) { _stopQty = pos.RemainingContracts; } + if (_stopQty > 0) { - CreateNewStopOrder(kvp.Key, pos.RemainingContracts, kvp.Value.StopPrice, kvp.Value.Direction); + CreateNewStopOrder(kvp.Key, _stopQty, kvp.Value.StopPrice, kvp.Value.Direction); // Build 950: Restore OCO-cascade-cancelled targets after stop replacement. if (kvp.Value.BracketRestorationNeeded && kvp.Value.CapturedTargets != null) { @@ -598,18 +601,22 @@ private void HandleMatchedFollowerOrder(string matchedEntry, PositionInfo matche if (_psr.Value.OldOrder == order) { PositionInfo _rPos; - if (activePositions.TryGetValue(_psr.Key, out _rPos) && _rPos.RemainingContracts > 0) + // Build 955: Move guard inside lock -- check and use same atomic snapshot. + if (activePositions.TryGetValue(_psr.Key, out _rPos)) { int _rQty; lock (stateLock) { _rQty = _rPos.RemainingContracts; } - CreateNewStopOrder(_psr.Key, _rQty, _psr.Value.StopPrice, _psr.Value.Direction); - if (_psr.Value.BracketRestorationNeeded && _psr.Value.CapturedTargets != null) + if (_rQty > 0) { - TargetSnapshot[] _snap = _psr.Value.CapturedTargets; - string _rKey = _psr.Key; - TriggerCustomEvent(o => RestoreCascadedTargets(_rKey, _snap), null); - } - } + CreateNewStopOrder(_psr.Key, _rQty, _psr.Value.StopPrice, _psr.Value.Direction); + if (_psr.Value.BracketRestorationNeeded && _psr.Value.CapturedTargets != null) + { + TargetSnapshot[] _snap = _psr.Value.CapturedTargets; + string _rKey = _psr.Key; + TriggerCustomEvent(o => RestoreCascadedTargets(_rKey, _snap), null); + } + } // if (_rQty > 0) + } // if (activePositions.TryGetValue) if (pendingStopReplacements.TryRemove(_psr.Key, out _)) Interlocked.Decrement(ref pendingReplacementCount); return; diff --git a/src/V12_002.Trailing.cs b/src/V12_002.Trailing.cs index c539b696..80da1f47 100644 --- a/src/V12_002.Trailing.cs +++ b/src/V12_002.Trailing.cs @@ -514,14 +514,26 @@ private void UpdateStopOrder(string entryName, PositionInfo pos, double newStopP if (currentStop != null && (currentStop.OrderState == OrderState.CancelPending || currentStop.OrderState == OrderState.Submitted)) { // Order is already being cancelled or submitted - queue the new stop price + // Build 955: Snapshot targets BEFORE TryAdd so any callback sees a fully-initialized record. + var _b955TargetsA = new System.Collections.Generic.List(); + for (int _tA = 1; _tA <= 5; _tA++) + { + var _tDA = GetTargetOrdersDictionary(_tA); + Order _tOA; + if (_tDA != null && _tDA.TryGetValue(entryName, out _tOA) && _tOA != null + && (_tOA.OrderState == OrderState.Working || _tOA.OrderState == OrderState.Accepted)) + _b955TargetsA.Add(new TargetSnapshot { TargetNum = _tA, Price = _tOA.LimitPrice, Qty = _tOA.Quantity, CapturedOrder = _tOA }); + } var newPending = new PendingStopReplacement { - EntryName = entryName, - Quantity = pos.RemainingContracts, - StopPrice = validatedStopPrice, - Direction = pos.Direction, - OldOrder = currentStop, - CreatedTime = DateTime.Now // V8.30: Timeout support + EntryName = entryName, + Quantity = pos.RemainingContracts, + StopPrice = validatedStopPrice, + Direction = pos.Direction, + OldOrder = currentStop, + CreatedTime = DateTime.Now, // V8.30: Timeout support + CapturedTargets = _b955TargetsA.Count > 0 ? _b955TargetsA.ToArray() : null, + BracketRestorationNeeded = _b955TargetsA.Count > 0 }; // V8.30: Thread-safe add or update @@ -558,21 +570,6 @@ private void UpdateStopOrder(string entryName, PositionInfo pos, double newStopP } } - // Build 950: Snapshot Working/Accepted targets before cancel for OCO cascade restoration. - { - var _b950Targets = new System.Collections.Generic.List(); - for (int _t = 1; _t <= 5; _t++) - { - var _tD = GetTargetOrdersDictionary(_t); - Order _tO; - if (_tD != null && _tD.TryGetValue(entryName, out _tO) && _tO != null - && (_tO.OrderState == OrderState.Working || _tO.OrderState == OrderState.Accepted)) - _b950Targets.Add(new TargetSnapshot { TargetNum = _t, Price = _tO.LimitPrice, Qty = _tO.Quantity, CapturedOrder = _tO }); - } - newPending.CapturedTargets = _b950Targets.Count > 0 ? _b950Targets.ToArray() : null; - newPending.BracketRestorationNeeded = _b950Targets.Count > 0; - } - pos.CurrentStopPrice = validatedStopPrice; pos.CurrentTrailLevel = newTrailLevel; Print(string.Format("V8.12: Stop update queued for {0} (current state: {1})", entryName, currentStop.OrderState)); @@ -581,14 +578,26 @@ private void UpdateStopOrder(string entryName, PositionInfo pos, double newStopP if (currentStop != null && (currentStop.OrderState == OrderState.Working || currentStop.OrderState == OrderState.Accepted)) { + // Build 955: Snapshot targets BEFORE TryAdd so any callback sees a fully-initialized record. + var _b955TargetsB = new System.Collections.Generic.List(); + for (int _tB = 1; _tB <= 5; _tB++) + { + var _tDB = GetTargetOrdersDictionary(_tB); + Order _tOB; + if (_tDB != null && _tDB.TryGetValue(entryName, out _tOB) && _tOB != null + && (_tOB.OrderState == OrderState.Working || _tOB.OrderState == OrderState.Accepted)) + _b955TargetsB.Add(new TargetSnapshot { TargetNum = _tB, Price = _tOB.LimitPrice, Qty = _tOB.Quantity, CapturedOrder = _tOB }); + } var newPending = new PendingStopReplacement { - EntryName = entryName, - Quantity = pos.RemainingContracts, - StopPrice = validatedStopPrice, - Direction = pos.Direction, - OldOrder = currentStop, - CreatedTime = DateTime.Now // V8.30: Timeout support + EntryName = entryName, + Quantity = pos.RemainingContracts, + StopPrice = validatedStopPrice, + Direction = pos.Direction, + OldOrder = currentStop, + CreatedTime = DateTime.Now, // V8.30: Timeout support + CapturedTargets = _b955TargetsB.Count > 0 ? _b955TargetsB.ToArray() : null, + BracketRestorationNeeded = _b955TargetsB.Count > 0 }; // V8.30: Thread-safe add @@ -603,21 +612,6 @@ private void UpdateStopOrder(string entryName, PositionInfo pos, double newStopP } } - // Build 950: Snapshot Working/Accepted targets before cancel for OCO cascade restoration. - { - var _b950Targets = new System.Collections.Generic.List(); - for (int _t = 1; _t <= 5; _t++) - { - var _tD = GetTargetOrdersDictionary(_t); - Order _tO; - if (_tD != null && _tD.TryGetValue(entryName, out _tO) && _tO != null - && (_tO.OrderState == OrderState.Working || _tO.OrderState == OrderState.Accepted)) - _b950Targets.Add(new TargetSnapshot { TargetNum = _t, Price = _tO.LimitPrice, Qty = _tO.Quantity, CapturedOrder = _tO }); - } - newPending.CapturedTargets = _b950Targets.Count > 0 ? _b950Targets.ToArray() : null; - newPending.BracketRestorationNeeded = _b950Targets.Count > 0; - } - if (pos.ExecutingAccount != null) { pos.ExecutingAccount.Cancel(new[] { currentStop }); diff --git a/src/V12_002.cs b/src/V12_002.cs index e9ecbd84..4171b70d 100644 --- a/src/V12_002.cs +++ b/src/V12_002.cs @@ -41,7 +41,7 @@ namespace NinjaTrader.NinjaScript.Strategies { public partial class V12_002 : Strategy { - public const string BUILD_TAG = "954"; // V12.954: Bot Audit Fixes (IPC ghost conn, RETEST null state, deprecated mode fallback) + public const string BUILD_TAG = "955"; // V12.955: Race condition fix -- snapshot-before-TryAdd; TOCTOU lock-guard on RemainingContracts; IPC dead code pruned #region Variables From e35028fa975ab66f79ff55ce14ebee914bb70211 Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 15:35:12 -0800 Subject: [PATCH 03/13] docs(955): Add Fresh PR Rule and Claude Agent Operation Protocol to Standards Manifesto - Section 6: Add Autonomous Pull Request Handover rule mandating fresh branches for bot audit submissions to ensure full-file audit sweeps - Section 12: Add Claude Agent Operation Protocol with Do-Not-Interrupt, .NET 4.8 Hardening Hook, Missing Brief Failsafe, and Autonomy Rule Co-Authored-By: Claude Sonnet 4.6 --- .agent/standards_manifesto.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.agent/standards_manifesto.md b/.agent/standards_manifesto.md index 4fa90c5e..ca0266dc 100644 --- a/.agent/standards_manifesto.md +++ b/.agent/standards_manifesto.md @@ -44,6 +44,11 @@ This document provides the immutable technical standards for all AI agents (Anth ## 6. Clean-Slate Repo Hygiene (The "Hygiene Rule") - **Zero-Delta Mandate**: Every new Mission (initialized via `$MISSION`) MUST start with a 0-delta `main` branch. If "Big Numbers" (large uncommitted/unmerged diffs > 100 lines) exist, the agent MUST recommend a cleanup/merge before starting new work. +- **Autonomous Pull Request Handover (The Fresh PR Rule)**: When submitting code for bot audit or human review, agents MUST NEVER push to an existing open Pull Request (e.g., updating a dirty branch). Instead, agents MUST: + 1. Checkout a completely new semantic branch (e.g., `build/955-audit-remediation`). + 2. Push the new branch and open a BRAND NEW Pull Request targeting `main`. + 3. Close any superseded or legacy PRs via the GitHub CLI, explicitly leaving a comment referencing the new clean PR. + _Why? Incrementally updating existing PRs can cause automated audit bots (Codex, Greptile, DeepSource) to miss context. A fresh PR triggers a 100% clean, full-file audit sweep._ - **Atomic Missions**: Every bug fix or feature MUST be its own branch and MUST be merged into `main` immediately upon verification (e.g. F5 compile in NT8). No "stacking" unrelated fixes in long-lived branches. - **Binary & Log Purge**: Never commit `.exe`, `.log`, `.bak`, or legacy backup folders to source control. They should be stashed, deleted, or added to `.gitignore`. - **Dashboard Cleanup**: Before ending a session, the agent MUST ensure all work is either Committed or the user has been guided to Merge. The goal is a +0/-0 dashboard between missions. @@ -156,6 +161,15 @@ OnLineInfo ... status=open <- live untracked GTC order at broker **Full discovery steps:** See `.agent/workflows/live-bug-triage.md` Section 0. +## 12. Claude Agent Operation Protocol (Usage Insights) + +**Based on historical friction data, all agents MUST adhere to these execution constraints:** + +- **The "Do Not Interrupt" Protocol:** Agents operating in standard execution mode should complete their logical batches and commit _autonomously_. Do not pause mid-task to ask for user check-ins unless explicitly blocked by a missing file or a hard compilation failure. +- **.NET 4.8 Hardening Hook:** Target framework is .NET 4.8. Do NOT use C# features unavailable in .NET 4.8 (e.g., range operators `[..]`, `Index`/`Range` types, default interface implementations). Always use `CultureInfo.InvariantCulture` for numeric parsing. This must be checked before every commit. +- **The "Missing Brief" Failsafe:** Before any phase starts, the Agent MUST verify that the referenced `implementation_plan.md` or `$MISSION` artifact exists on disk. If it does not, the Agent MUST halt and ask the user for the brief, rather than attempting to guess or reverse-engineer the plan via codebase searches. +- **Autonomy Rule (Default to Action):** Agents are empowered and EXPECTED to execute the full end-to-end lifecycle of a task autonomously. This includes branch creation, surgical implementation, local verification (compile/ASCII), git committing, pushing, and opening/updating PRs. Do not wait for manual approval to move from "Code Change" to "Git Push" if local verification (`deploy-sync.ps1`) passes. + --- > [!NOTE] From ad60a1795acccdf92d5f48491239f63d743fbbbb Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 16:26:24 -0800 Subject: [PATCH 04/13] fix(956): DeepSource remediation -- RETEST_MANUAL null cleanup + IPC dead code removal FINDING 1 -- RETEST_MANUAL null-submit fall-through (Retest.cs, lines 326-331) The Build 954 return guard was applied only to the auto-RETEST path. The RETEST_MANUAL path had no return after the null check, allowing execution to fall through to: - entryOrders[entryName] = entryOrder (assigns null) - ExecuteSmartDispatchEntry(...) (SIMA dispatch with null order) Fix: add activePositions.TryRemove + return to RETEST_MANUAL path to mirror auto-RETEST. FINDING 2 -- auto-RETEST orphaned activePositions on null submit (Retest.cs, lines 185-189) activePositions[entryName] was pre-registered before submit but not cleaned up on null return. Fix: add activePositions.TryRemove(entryName, out _) before the existing return. FINDING 3 -- IPC dead code (V12_001.cs) ConnectToStrategy() body was emptied in Build 955 but still wired: - Task.Run(() => ConnectToStrategy()) still fired on Realtime (line 369) - if (!isConnected) ConnectToStrategy() still called from SendCommand() - ReceiveLoop() and ScheduleReconnect() still defined but never called Fix: remove wiring calls, delete empty ConnectToStrategy(), delete dead methods. Verification: ASCII gate PASS (deploy-sync.ps1). Pending F5 compile in NT8. Supersedes PR #25 (build/955-audit-remediation) per Fresh PR Rule. Co-Authored-By: Claude Sonnet 4.6 --- src/V12_001.cs | 128 +--------------------------------- src/V12_002.Entries.Retest.cs | 3 + src/V12_002.cs | 2 +- 3 files changed, 6 insertions(+), 127 deletions(-) diff --git a/src/V12_001.cs b/src/V12_001.cs index 60402735..49a75615 100644 --- a/src/V12_001.cs +++ b/src/V12_001.cs @@ -366,7 +366,7 @@ protected override void OnStateChange() else if (State == State.Realtime) { activeSymbol = Instrument.MasterInstrument.Name; - if (AutoConnect) Task.Run(() => ConnectToStrategy()); + // [Build 956]: IPC deprecated -- ConnectToStrategy() removed. AutoConnect is a no-op. } else if (State == State.Terminated) { @@ -2996,12 +2996,6 @@ private void SyncAll_Click(object sender, RoutedEventArgs e) #region IPC Communication - private void ConnectToStrategy() - { - // [Build 954]: IPC deprecated. Strategy no longer hosts IPC server (Phase 6 pruning). - // [Build 955]: Dead code removed. SendCommand() is safely no-oped via tcpStream null guard. - } - private void DisconnectFromStrategy() { isShuttingDown = true; @@ -3022,8 +3016,6 @@ private void SendCommand(string command) { try { - if (!isConnected) ConnectToStrategy(); - lock (tcpLock) { if (tcpStream != null && tcpStream.CanWrite) @@ -3047,123 +3039,7 @@ private void SendCommand(string command) }); } - private void ReceiveLoop() - { - StringBuilder buffer = new StringBuilder(); - byte[] readBuffer = new byte[4096]; - - try - { - while (isConnected) - { - try - { - if (tcpStream == null || !tcpStream.CanRead) break; - - int bytesRead = tcpStream.Read(readBuffer, 0, readBuffer.Length); - if (bytesRead == 0) - { - Print("V12.14: ReceiveLoop - server closed connection (0 bytes)"); - break; - } - - buffer.Append(Encoding.UTF8.GetString(readBuffer, 0, bytesRead)); - - string data = buffer.ToString(); - int newlineIdx; - while ((newlineIdx = data.IndexOf('\n')) >= 0) - { - string message = data.Substring(0, newlineIdx).Trim(); - data = data.Substring(newlineIdx + 1); - - if (!string.IsNullOrEmpty(message)) - responseQueue.Enqueue(message); - } - buffer.Clear(); - buffer.Append(data); - } - catch (Exception ex) - { - Print($"V12.14: ReceiveLoop exception - {ex.Message}"); - break; - } - } - } - finally - { - // V12.14: Cleanup - mark disconnected and schedule reconnect - Print("V12.14: ReceiveLoop exited - marking disconnected"); - lock (tcpLock) - { - isConnected = false; - try { tcpStream?.Close(); } catch { } - try { tcpClient?.Close(); } catch { } - tcpStream = null; - tcpClient = null; - } - - if (ChartControl != null) - { - ChartControl.Dispatcher.BeginInvoke(new Action(() => - { - if (hubStatusLed != null) - { - hubStatusLed.Background = TextMuted; - hubStatusLed.ToolTip = "IPC Disconnected"; - } - })); - } - - ScheduleReconnect(); - } - } - - // V12.14: Auto-reconnect after unexpected disconnect - // V12.1101E [B-8]: Refactored to non-recursive -- reuses a single timer object via .Change() - // instead of Dispose + new Timer on each failure. Prevents timer object accumulation and - // eliminates recursive call stack growth under sustained disconnection. - private readonly object _reconnectLock = new object(); - - private void ScheduleReconnect() - { - if (isShuttingDown || isConnected) return; - - lock (_reconnectLock) - { - if (reconnectTimer != null) - { - // Reset the existing timer to fire again in 3 s -- no new allocation needed - reconnectTimer.Change(3000, Timeout.Infinite); - return; - } - - // First time: create the timer once; subsequent reconnect calls reuse it via .Change() - reconnectTimer = new System.Threading.Timer(_ => - { - if (isShuttingDown || isConnected) return; - - // [Build 934]: Removed per-attempt "Auto-reconnect attempting..." print -- now throttled in ConnectToStrategy() catch block - try - { - ConnectToStrategy(); - if (isConnected) - { - Print("V12 Panel: Strategy came online -- connected ?"); - lock (_reconnectLock) { reconnectTimer = null; } - } - else - { - ScheduleReconnect(); - } - } - catch (Exception ex) - { - // [Build 934]: Logging is throttled inside ConnectToStrategy() catch -- no extra print here - ScheduleReconnect(); - } - }, null, 3000, Timeout.Infinite); - } - } + // [Build 956]: ReceiveLoop() and ScheduleReconnect() removed -- IPC deprecated (Phase 6 pruning). private void ProcessStrategyResponse(string response) { diff --git a/src/V12_002.Entries.Retest.cs b/src/V12_002.Entries.Retest.cs index 2d4e6646..9e7bc9bf 100644 --- a/src/V12_002.Entries.Retest.cs +++ b/src/V12_002.Entries.Retest.cs @@ -185,6 +185,7 @@ private void ExecuteRetestEntry(int contracts) if (entryOrder == null) { AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRetest); + activePositions.TryRemove(entryName, out _); // [Build 956]: Clean pre-registered state on null submit. Print("[ERROR][1102Y-V3] RETEST SubmitOrderUnmanaged NULL for " + entryName + " -- rolled back."); return; // [Build 954]: Do not latch session or dispatch SIMA for a failed order. } @@ -326,7 +327,9 @@ private void ExecuteRetestManualEntry(double manualPrice, MarketPosition directi if (entryOrder == null) { AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRetestMnl); + activePositions.TryRemove(entryName, out _); // [Build 956]: Clean pre-registered state on null submit. Print("[ERROR][1102Y-V3] RETEST_MANUAL SubmitOrderUnmanaged NULL for " + entryName + " -- rolled back."); + return; // [Build 956]: Do not assign null entryOrder or dispatch SIMA for a failed order. } entryOrders[entryName] = entryOrder; diff --git a/src/V12_002.cs b/src/V12_002.cs index 4171b70d..cd1a420b 100644 --- a/src/V12_002.cs +++ b/src/V12_002.cs @@ -41,7 +41,7 @@ namespace NinjaTrader.NinjaScript.Strategies { public partial class V12_002 : Strategy { - public const string BUILD_TAG = "955"; // V12.955: Race condition fix -- snapshot-before-TryAdd; TOCTOU lock-guard on RemainingContracts; IPC dead code pruned + public const string BUILD_TAG = "956"; // V12.956: DeepSource remediation -- RETEST_MANUAL null cleanup + IPC dead code removal (ReceiveLoop/ScheduleReconnect) #region Variables From 13a22185e2ae4e8099dc5dae1c03c77cfe35d862 Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 16:35:30 -0800 Subject: [PATCH 05/13] ci: add openai aider workflow for automated audits --- .github/workflows/codex-audit.yml | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/codex-audit.yml diff --git a/.github/workflows/codex-audit.yml b/.github/workflows/codex-audit.yml new file mode 100644 index 00000000..c6e88260 --- /dev/null +++ b/.github/workflows/codex-audit.yml @@ -0,0 +1,43 @@ +name: Codex Pre-Triage PR Audit + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main, master] + +jobs: + codex-audit: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Aider (OpenAI CLI) + run: pip install aider-chat + + - name: Run Codex Audit via OpenAI + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + # Use Aider in headless mode to execute the audit using GPT-4o + aider --model gpt-4o --no-auto-commits --yes-always --message "Execute audit mission: .agent/brain/codex_pr25_audit.md. Read the workflows and write the final P1/P2/P3 severity report safely to audit_output.md without modifying any source files." + + - name: Post Audit Results to PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ -f audit_output.md ]; then + gh pr comment ${{ github.event.pull_request.number }} --body-file audit_output.md + else + echo "Audit output file not found." + # Fallback if Aider stdout'd it instead + gh pr comment ${{ github.event.pull_request.number }} --body "The OpenAI audit finished, but the markdown file was not saved correctly. Please check action logs." + fi From 2a5781fd9be6016b24832cd97f4aeed15d7a4957 Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 16:38:19 -0800 Subject: [PATCH 06/13] fix(956): remove empty catch blocks --- src/V12_001.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/V12_001.cs b/src/V12_001.cs index 49a75615..66ffa061 100644 --- a/src/V12_001.cs +++ b/src/V12_001.cs @@ -3003,8 +3003,8 @@ private void DisconnectFromStrategy() lock (tcpLock) { isConnected = false; - try { tcpStream?.Close(); } catch { } - try { tcpClient?.Close(); } catch { } + tcpStream?.Close(); + tcpClient?.Close(); tcpStream = null; tcpClient = null; } From c8037b479ebb7cf99ea1ee4174d80414c0a4e30b Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 17:28:27 -0800 Subject: [PATCH 07/13] ci: fix github action permissions for aider pr comments --- .github/workflows/codex-audit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/codex-audit.yml b/.github/workflows/codex-audit.yml index c6e88260..68431e85 100644 --- a/.github/workflows/codex-audit.yml +++ b/.github/workflows/codex-audit.yml @@ -5,6 +5,10 @@ on: types: [opened, synchronize, reopened] branches: [main, master] +permissions: + pull-requests: write + contents: read + jobs: codex-audit: runs-on: ubuntu-latest From b24e1c848d7ef9ba20116f86f701be9bffe8812d Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Fri, 6 Mar 2026 17:40:04 -0800 Subject: [PATCH 08/13] fix(956): remove unused isConnected and isShuttingDown fields --- .github/workflows/codex-audit.yml | 2 +- src/V12_001.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/codex-audit.yml b/.github/workflows/codex-audit.yml index 68431e85..9f7e6057 100644 --- a/.github/workflows/codex-audit.yml +++ b/.github/workflows/codex-audit.yml @@ -32,7 +32,7 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | # Use Aider in headless mode to execute the audit using GPT-4o - aider --model gpt-4o --no-auto-commits --yes-always --message "Execute audit mission: .agent/brain/codex_pr25_audit.md. Read the workflows and write the final P1/P2/P3 severity report safely to audit_output.md without modifying any source files." + aider --model gpt-4o --no-auto-commits --yes-always --message "Execute audit mission: .agent/brain/codex_pr25_audit.md. Read the workflows and write the final P1/P2/P3 severity report safely to audit_output.md without modifying any source files." > audit_output.md - name: Post Audit Results to PR env: diff --git a/src/V12_001.cs b/src/V12_001.cs index 66ffa061..ec2e192d 100644 --- a/src/V12_001.cs +++ b/src/V12_001.cs @@ -164,8 +164,6 @@ public void SetSettings(string mode, int count, V12ModeSettings settings) private NetworkStream tcpStream; private readonly object tcpLock = new object(); private Thread receiveThread; - private volatile bool isConnected = false; - private volatile bool isShuttingDown = false; private System.Threading.Timer reconnectTimer; private ConcurrentQueue responseQueue = new ConcurrentQueue(); // [Build 934]: Throttle IPC retry log spam -- print on 1st failure then once per 60 s @@ -2998,11 +2996,9 @@ private void SyncAll_Click(object sender, RoutedEventArgs e) private void DisconnectFromStrategy() { - isShuttingDown = true; reconnectTimer?.Dispose(); lock (tcpLock) { - isConnected = false; tcpStream?.Close(); tcpClient?.Close(); tcpStream = null; @@ -3034,7 +3030,6 @@ private void SendCommand(string command) catch (Exception ex) { Print($"V12 STD: Send error - {ex.Message}"); - isConnected = false; } }); } From 0682e04d280433517dd7b7cfb04047730f3143ec Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Sat, 7 Mar 2026 14:00:30 -0800 Subject: [PATCH 09/13] fix(build-959): add lock(stateLock) guards to Retest dict mutations Codex PR #27 audit flagged all activePositions / entryOrders writes in ExecuteRetestEntry and ExecuteRetestManualEntry as missing the StateLock Rule (Manifesto S2). Six writes wrapped in lock(stateLock) inline blocks: - activePositions[entryName] = pos (both methods) - activePositions.TryRemove in null-submit rollback (both methods) - entryOrders[entryName] = entryOrder (both methods) No logic change -- TryRemove + early return already present from Build 956. Co-Authored-By: Claude Sonnet 4.6 --- src/V12_002.Entries.Retest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/V12_002.Entries.Retest.cs b/src/V12_002.Entries.Retest.cs index 9e7bc9bf..3cc741b8 100644 --- a/src/V12_002.Entries.Retest.cs +++ b/src/V12_002.Entries.Retest.cs @@ -171,7 +171,7 @@ private void ExecuteRetestEntry(int contracts) }; ApplyTargetLadderGuard(pos); - activePositions[entryName] = pos; + lock (stateLock) { activePositions[entryName] = pos; } // Build 1102Y-V3 [MS-07]: Register Master expected BEFORE Limit entry. int masterDeltaRetest = (direction == MarketPosition.Long) ? contracts : -contracts; @@ -185,12 +185,12 @@ private void ExecuteRetestEntry(int contracts) if (entryOrder == null) { AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRetest); - activePositions.TryRemove(entryName, out _); // [Build 956]: Clean pre-registered state on null submit. + lock (stateLock) { activePositions.TryRemove(entryName, out _); } // [Build 956]: Clean pre-registered state on null submit. Print("[ERROR][1102Y-V3] RETEST SubmitOrderUnmanaged NULL for " + entryName + " -- rolled back."); return; // [Build 954]: Do not latch session or dispatch SIMA for a failed order. } - entryOrders[entryName] = entryOrder; + lock (stateLock) { entryOrders[entryName] = entryOrder; } retestFiredThisSession = true; // V12.1101E [B-2]: Arm latch -- no further RETEST entries this session Print(string.Format("RETEST ENTRY ORDER: {0} {1}@{2:F2} | ATR: {3:F2}", signalName, contracts, entryPrice, currentATR)); @@ -313,7 +313,7 @@ private void ExecuteRetestManualEntry(double manualPrice, MarketPosition directi }; ApplyTargetLadderGuard(pos); - activePositions[entryName] = pos; + lock (stateLock) { activePositions[entryName] = pos; } // Build 1102Y-V3 [MS-08]: Register Master expected BEFORE Limit entry. int masterDeltaRetestMnl = (direction == MarketPosition.Long) ? contracts : -contracts; @@ -327,11 +327,11 @@ private void ExecuteRetestManualEntry(double manualPrice, MarketPosition directi if (entryOrder == null) { AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRetestMnl); - activePositions.TryRemove(entryName, out _); // [Build 956]: Clean pre-registered state on null submit. + lock (stateLock) { activePositions.TryRemove(entryName, out _); } // [Build 956]: Clean pre-registered state on null submit. Print("[ERROR][1102Y-V3] RETEST_MANUAL SubmitOrderUnmanaged NULL for " + entryName + " -- rolled back."); return; // [Build 956]: Do not assign null entryOrder or dispatch SIMA for a failed order. } - entryOrders[entryName] = entryOrder; + lock (stateLock) { entryOrders[entryName] = entryOrder; } Print(string.Format("V12.27 RETEST_MANUAL: {0} {1}@{2:F2} LIMIT | Stop: {3:F2} | RMA Targets", direction, contracts, entryPrice, stopPrice)); From c5c4c206d2eff8e7e015cff40fff0d60cbf1d3eb Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Sun, 8 Mar 2026 11:31:02 -0700 Subject: [PATCH 10/13] build(ci): sync new Vertex AI PR auditor to branch --- .github/workflows/gemini-pr-audit.yml | 96 +++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/gemini-pr-audit.yml diff --git a/.github/workflows/gemini-pr-audit.yml b/.github/workflows/gemini-pr-audit.yml new file mode 100644 index 00000000..bc7f6fe9 --- /dev/null +++ b/.github/workflows/gemini-pr-audit.yml @@ -0,0 +1,96 @@ +name: Gemini PR Auditor + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + gemini-audit: + name: Gemini 3.1 Pro (High) Auto-Auditor + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract PR diff + id: diff + run: | + git diff origin/${{ github.event.pull_request.base.ref }}...HEAD > pr.diff + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@v2 + with: + credentials_json: "${{ secrets.GCP_CREDENTIALS }}" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install Vertex AI SDK + run: npm install @google-cloud/vertexai + + - name: Run Vertex AI Audit and Post Comment + uses: actions/github-script@v7 + env: + GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + GCP_REGION: us-central1 + with: + script: | + const fs = require('fs'); + const { VertexAI } = require('@google-cloud/vertexai'); + + const diffContent = fs.readFileSync('pr.diff', 'utf8'); + let geminiMdContent = ''; + try { + geminiMdContent = fs.readFileSync('GEMINI.md', 'utf8'); + } catch (e) { + console.log('GEMINI.md not found in the root directory, proceeding without it.'); + } + + const prompt = `You are the Gemini CLI (Antigravity Auto-Auditor) acting as a PR reviewer. + Your task is to analyze the following PR diff strictly against our project's GEMINI.md standards (Zero-Trust IPC, FSM mutations, etc.). + + Here are the GEMINI.md standards: + ${geminiMdContent} + + Here is the pull request diff: + ${diffContent} + + Provide a systematic review of the changes: + 1. Evaluate strict adherence to the standards (e.g., FSM replace pattern vs cancel+submit, lock guards, headless UI rules). + 2. Point out any violations of the standard with specific file and line context. + 3. If the code is perfectly aligned, state that it passes the Zero-Regression Bot Audit. + + Be thorough, precise, and uncompromising on the rules.`; + + const vertex_ai = new VertexAI({project: process.env.GCP_PROJECT_ID, location: process.env.GCP_REGION}); + // Gemini 3.1 Pro Preview via Vertex AI + const generativeModel = vertex_ai.preview.getGenerativeModel({ + model: 'gemini-3.1-pro-preview-customtools', + }); + + let reviewText = ''; + try { + const resp = await generativeModel.generateContent({ + contents: [{role: 'user', parts: [{text: prompt}]}], + }); + reviewText = resp.response.candidates[0].content.parts[0].text; + } catch (error) { + console.error("Error calling Vertex AI API:", error); + reviewText = "⚠️ **Vertex AI Audit Failed to Execute**: " + error.message; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: "### 🤖 Antigravity Auto-Auditor (Gemini 3.1 Pro via Vertex AI)\n\n" + reviewText + }); From becfe5c1563a7b08c1a34f1b6691c3b6952ac1af Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Sun, 8 Mar 2026 11:36:21 -0700 Subject: [PATCH 11/13] build(ci): downgrade Vertex AI model to stable gemini-1.5-pro --- .github/workflows/gemini-pr-audit.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gemini-pr-audit.yml b/.github/workflows/gemini-pr-audit.yml index bc7f6fe9..7131d906 100644 --- a/.github/workflows/gemini-pr-audit.yml +++ b/.github/workflows/gemini-pr-audit.yml @@ -6,7 +6,7 @@ on: jobs: gemini-audit: - name: Gemini 3.1 Pro (High) Auto-Auditor + name: Gemini 1.5 Pro (High) Auto-Auditor runs-on: ubuntu-latest permissions: contents: read @@ -72,9 +72,9 @@ jobs: Be thorough, precise, and uncompromising on the rules.`; const vertex_ai = new VertexAI({project: process.env.GCP_PROJECT_ID, location: process.env.GCP_REGION}); - // Gemini 3.1 Pro Preview via Vertex AI + // Gemini 1.5 Pro via Vertex AI const generativeModel = vertex_ai.preview.getGenerativeModel({ - model: 'gemini-3.1-pro-preview-customtools', + model: 'gemini-1.5-pro', }); let reviewText = ''; @@ -92,5 +92,5 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - body: "### 🤖 Antigravity Auto-Auditor (Gemini 3.1 Pro via Vertex AI)\n\n" + reviewText + body: "### 🤖 Antigravity Auto-Auditor (Gemini 1.5 Pro via Vertex AI)\n\n" + reviewText }); From ca5a04b2dfa1a205cf73dcab7c9c8f3f31be8f69 Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Sun, 8 Mar 2026 11:38:21 -0700 Subject: [PATCH 12/13] build(ci): upgrade Vertex AI model to gemini-2.5-pro --- .github/workflows/gemini-pr-audit.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gemini-pr-audit.yml b/.github/workflows/gemini-pr-audit.yml index 7131d906..d486b3db 100644 --- a/.github/workflows/gemini-pr-audit.yml +++ b/.github/workflows/gemini-pr-audit.yml @@ -6,7 +6,7 @@ on: jobs: gemini-audit: - name: Gemini 1.5 Pro (High) Auto-Auditor + name: Gemini 2.5 Pro (High) Auto-Auditor runs-on: ubuntu-latest permissions: contents: read @@ -72,9 +72,9 @@ jobs: Be thorough, precise, and uncompromising on the rules.`; const vertex_ai = new VertexAI({project: process.env.GCP_PROJECT_ID, location: process.env.GCP_REGION}); - // Gemini 1.5 Pro via Vertex AI + // Gemini 2.5 Pro via Vertex AI const generativeModel = vertex_ai.preview.getGenerativeModel({ - model: 'gemini-1.5-pro', + model: 'gemini-2.5-pro', }); let reviewText = ''; @@ -92,5 +92,5 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - body: "### 🤖 Antigravity Auto-Auditor (Gemini 1.5 Pro via Vertex AI)\n\n" + reviewText + body: "### 🤖 Antigravity Auto-Auditor (Gemini 2.5 Pro via Vertex AI)\n\n" + reviewText }); From 70379908f4c8a6d1bb0f75341ec869537a60147a Mon Sep 17 00:00:00 2001 From: "AI M. Khalid" Date: Sun, 8 Mar 2026 13:02:28 -0700 Subject: [PATCH 13/13] fix(core): cycle 1 - restore IPC stack per Director audit Restores ConnectToStrategy(), ReceiveLoop(), and ScheduleReconnect() to src/V12_001.cs, which were incorrectly removed in a prior commit tagged as Phase 6 pruning. The UI Hub panel still relies on TCP IPC to communicate with V12_002 strategy (SendCommand is called ~30+ times). Also restores: - volatile bool isConnected / isShuttingDown fields - _reconnectLock object field - AutoConnect Task.Run() in State.Realtime - isShuttingDown/isConnected state mgmt in DisconnectFromStrategy() - lazy-connect guard + isConnected=false reset in SendCommand() UI mode RMA fallback fix (Build 954) and lock(stateLock) guards in V12_002.Entries.Retest.cs are unchanged and remain in place. Forensic checks: 0 raw Cancel()/Submit() patterns introduced, ASCII gate PASS, no non-ASCII bytes in any string literal. Co-Authored-By: Claude Sonnet 4.6 --- src/V12_001.cs | 196 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 4 deletions(-) diff --git a/src/V12_001.cs b/src/V12_001.cs index ec2e192d..d79f1682 100644 --- a/src/V12_001.cs +++ b/src/V12_001.cs @@ -164,6 +164,8 @@ public void SetSettings(string mode, int count, V12ModeSettings settings) private NetworkStream tcpStream; private readonly object tcpLock = new object(); private Thread receiveThread; + private volatile bool isConnected = false; + private volatile bool isShuttingDown = false; private System.Threading.Timer reconnectTimer; private ConcurrentQueue responseQueue = new ConcurrentQueue(); // [Build 934]: Throttle IPC retry log spam -- print on 1st failure then once per 60 s @@ -364,7 +366,7 @@ protected override void OnStateChange() else if (State == State.Realtime) { activeSymbol = Instrument.MasterInstrument.Name; - // [Build 956]: IPC deprecated -- ConnectToStrategy() removed. AutoConnect is a no-op. + if (AutoConnect) Task.Run(() => ConnectToStrategy()); } else if (State == State.Terminated) { @@ -2994,13 +2996,80 @@ private void SyncAll_Click(object sender, RoutedEventArgs e) #region IPC Communication + private void ConnectToStrategy() + { + try + { + lock (tcpLock) + { + if (isConnected) return; + + tcpClient = new TcpClient(); + tcpClient.Connect("127.0.0.1", IpcPort); + tcpStream = tcpClient.GetStream(); + isConnected = true; + // [Build 934]: Reset retry counters on successful connect + _ipcRetryCount = 0; + _lastRetryLogTime = DateTime.MinValue; + + Print($"V12 Panel: Strategy connected on port {IpcPort} ?"); + + if (ChartControl != null) + { + ChartControl.Dispatcher.BeginInvoke(new Action(() => + { + if (hubStatusLed != null) + { + hubStatusLed.Background = GreenFg; + hubStatusLed.ToolTip = "IPC Connected"; + } + })); + } + + receiveThread = new Thread(ReceiveLoop) { IsBackground = true, Name = "V12_Std_Receive" }; + receiveThread.Start(); + + SendCommand("GET_LAYOUT"); + } + } + catch (Exception) + { + isConnected = false; + _ipcRetryCount++; + + // [Build 934]: Log only on first failure and then at most once per 60 seconds + if (_ipcRetryCount == 1 || (DateTime.Now - _lastRetryLogTime).TotalSeconds >= 60) + { + Print($"V12 Panel: Strategy offline -- retrying in background (attempt #{_ipcRetryCount})"); + _lastRetryLogTime = DateTime.Now; + } + + if (ChartControl != null) + { + ChartControl.Dispatcher.BeginInvoke(new Action(() => + { + if (hubStatusLed != null) + { + hubStatusLed.Background = TextMuted; + hubStatusLed.ToolTip = "Waiting for Strategy (retrying...)"; + } + })); + } + + // [Build 933]: Start retry loop on initial failure (market closed / Strategy not yet live). + ScheduleReconnect(); + } + } + private void DisconnectFromStrategy() { + isShuttingDown = true; reconnectTimer?.Dispose(); lock (tcpLock) { - tcpStream?.Close(); - tcpClient?.Close(); + isConnected = false; + try { tcpStream?.Close(); } catch { } + try { tcpClient?.Close(); } catch { } tcpStream = null; tcpClient = null; } @@ -3012,6 +3081,8 @@ private void SendCommand(string command) { try { + if (!isConnected) ConnectToStrategy(); + lock (tcpLock) { if (tcpStream != null && tcpStream.CanWrite) @@ -3030,11 +3101,128 @@ private void SendCommand(string command) catch (Exception ex) { Print($"V12 STD: Send error - {ex.Message}"); + isConnected = false; } }); } - // [Build 956]: ReceiveLoop() and ScheduleReconnect() removed -- IPC deprecated (Phase 6 pruning). + private void ReceiveLoop() + { + StringBuilder buffer = new StringBuilder(); + byte[] readBuffer = new byte[4096]; + + try + { + while (isConnected) + { + try + { + if (tcpStream == null || !tcpStream.CanRead) break; + + int bytesRead = tcpStream.Read(readBuffer, 0, readBuffer.Length); + if (bytesRead == 0) + { + Print("V12.14: ReceiveLoop - server closed connection (0 bytes)"); + break; + } + + buffer.Append(Encoding.UTF8.GetString(readBuffer, 0, bytesRead)); + + string data = buffer.ToString(); + int newlineIdx; + while ((newlineIdx = data.IndexOf('\n')) >= 0) + { + string message = data.Substring(0, newlineIdx).Trim(); + data = data.Substring(newlineIdx + 1); + + if (!string.IsNullOrEmpty(message)) + responseQueue.Enqueue(message); + } + buffer.Clear(); + buffer.Append(data); + } + catch (Exception ex) + { + Print($"V12.14: ReceiveLoop exception - {ex.Message}"); + break; + } + } + } + finally + { + // V12.14: Cleanup - mark disconnected and schedule reconnect + Print("V12.14: ReceiveLoop exited - marking disconnected"); + lock (tcpLock) + { + isConnected = false; + try { tcpStream?.Close(); } catch { } + try { tcpClient?.Close(); } catch { } + tcpStream = null; + tcpClient = null; + } + + if (ChartControl != null) + { + ChartControl.Dispatcher.BeginInvoke(new Action(() => + { + if (hubStatusLed != null) + { + hubStatusLed.Background = TextMuted; + hubStatusLed.ToolTip = "IPC Disconnected"; + } + })); + } + + ScheduleReconnect(); + } + } + + // V12.14: Auto-reconnect after unexpected disconnect + // V12.1101E [B-8]: Refactored to non-recursive -- reuses a single timer object via .Change() + // instead of Dispose + new Timer on each failure. Prevents timer object accumulation and + // eliminates recursive call stack growth under sustained disconnection. + private readonly object _reconnectLock = new object(); + + private void ScheduleReconnect() + { + if (isShuttingDown || isConnected) return; + + lock (_reconnectLock) + { + if (reconnectTimer != null) + { + // Reset the existing timer to fire again in 3 s -- no new allocation needed + reconnectTimer.Change(3000, Timeout.Infinite); + return; + } + + // First time: create the timer once; subsequent reconnect calls reuse it via .Change() + reconnectTimer = new System.Threading.Timer(_ => + { + if (isShuttingDown || isConnected) return; + + // [Build 934]: Removed per-attempt "Auto-reconnect attempting..." print -- now throttled in ConnectToStrategy() catch block + try + { + ConnectToStrategy(); + if (isConnected) + { + Print("V12 Panel: Strategy came online -- connected ?"); + lock (_reconnectLock) { reconnectTimer = null; } + } + else + { + ScheduleReconnect(); + } + } + catch (Exception ex) + { + // [Build 934]: Logging is throttled inside ConnectToStrategy() catch -- no extra print here + ScheduleReconnect(); + } + }, null, 3000, Timeout.Infinite); + } + } private void ProcessStrategyResponse(string response) {