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