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); + } }