From d0b27b89576bc2e1b73e87c9442bfffe81337879 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 10 Jul 2026 04:12:57 -0400 Subject: [PATCH] =?UTF-8?q?feat(inbound-api):=20InvalidateMethod=20seam=20?= =?UTF-8?q?purging=20handler=20+=20known-bad=20=E2=80=94=20consumer=20entr?= =?UTF-8?q?y=20point=20for=20the=20script-artifact=20invalidation=20contra?= =?UTF-8?q?ct=20(plan=2005)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj --- .../InboundScriptExecutor.cs | 18 +++++++++++++++--- .../ManagementActor.cs | 2 +- .../InboundScriptExecutorStalenessTests.cs | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs index bcfb0714..782db41a 100644 --- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs @@ -110,14 +110,26 @@ public class InboundScriptExecutor } /// - /// Removes a compiled script handler for a method name. + /// Drops the compiled handler AND any known-bad record for the method. The + /// consumption seam for the script-artifact invalidation contract (bundle import, + /// out-of-band updates, failover activation). Safe to call for unknown names. + /// The per-request revision check in ExecuteAsync remains the correctness + /// fallback when an invalidation is missed. /// - /// The method name to remove. - public void RemoveHandler(string methodName) + /// The method name to invalidate. + public void InvalidateMethod(string methodName) { _scriptHandlers.TryRemove(methodName, out _); + _knownBadMethods.TryRemove(methodName, out _); } + /// + /// Removes a compiled script handler for a method name. Delegates to + /// (public API preserved). + /// + /// The method name to remove. + public void RemoveHandler(string methodName) => InvalidateMethod(methodName); + /// /// Compiles and registers a single API method script. Returns false if the /// script is empty, fails Roslyn compilation, or violates the script trust model. diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs index c78184f8..29e2eda1 100644 --- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs @@ -2651,7 +2651,7 @@ public class ManagementActor : ReceiveActor await repo.DeleteApiMethodAsync(cmd.ApiMethodId); await repo.SaveChangesAsync(); if (method != null) - sp.GetService()?.RemoveHandler(method.Name); + sp.GetService()?.InvalidateMethod(method.Name); await AuditAsync(sp, user, "Delete", "ApiMethod", cmd.ApiMethodId.ToString(), method?.Name ?? cmd.ApiMethodId.ToString(), null); return true; } diff --git a/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorStalenessTests.cs b/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorStalenessTests.cs index 3f39c481..88f8e8d0 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorStalenessTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/InboundScriptExecutorStalenessTests.cs @@ -86,4 +86,21 @@ public class InboundScriptExecutorStalenessTests Assert.True(r.Success); Assert.Contains("99", r.ResultJson); } + + [Fact] + public async Task InvalidateMethod_DropsHandlerAndKnownBadRecord() + { + var good = new ApiMethod("inv", "return 5;") { Id = 5, TimeoutSeconds = 10 }; + Assert.True(_executor.CompileAndRegister(good)); + var broken = new ApiMethod("invbad", "%%%") { Id = 6, TimeoutSeconds = 10 }; + Assert.False(_executor.CompileAndRegister(broken)); + + _executor.InvalidateMethod("inv"); + _executor.InvalidateMethod("invbad"); + + Assert.Equal(0, _executor.KnownBadMethodCount); + // After invalidation the method lazily recompiles from the row — still serves. + var r = await Run(good); + Assert.True(r.Success); + } }