diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs
new file mode 100644
index 00000000..c9f371b6
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs
@@ -0,0 +1,49 @@
+using System.Text.Json;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
+
+///
+/// Reads the single monitored attribute of a ValueChange or Conditional script
+/// trigger (Flow Debugger script entry, design 2026-09-08 §2). The config keys are the ones the
+/// Central UI's ScriptTriggerConfigCodec writes (attributeName) and SiteRuntime's
+/// TriggerRouting also accepts (attribute). Pure; never throws on bad JSON.
+///
+public static class ScriptTriggerAttribute
+{
+ /// True for the trigger kinds the Flow Debugger fires by WRITING the monitored attribute.
+ public static bool IsWriteKind(string? triggerType) =>
+ string.Equals(triggerType, "ValueChange", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(triggerType, "Conditional", StringComparison.OrdinalIgnoreCase);
+
+ ///
+ /// Returns the monitored attribute for a write-kind trigger with a readable configuration;
+ /// false (and null) for every other kind, and for a write kind whose config is missing,
+ /// malformed, not an object, or names no attribute.
+ ///
+ public static bool TryRead(string? triggerType, string? triggerConfigurationJson, out string? attribute)
+ {
+ attribute = null;
+ if (!IsWriteKind(triggerType) || string.IsNullOrWhiteSpace(triggerConfigurationJson))
+ return false;
+ try
+ {
+ using var doc = JsonDocument.Parse(triggerConfigurationJson);
+ var root = doc.RootElement;
+ if (root.ValueKind != JsonValueKind.Object) return false;
+ foreach (var key in new[] { "attributeName", "attribute" })
+ {
+ if (root.TryGetProperty(key, out var v) && v.ValueKind == JsonValueKind.String
+ && v.GetString() is { Length: > 0 } name)
+ {
+ attribute = name;
+ return true;
+ }
+ }
+ return false;
+ }
+ catch (JsonException)
+ {
+ return false;
+ }
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Scripts/ScriptTriggerAttributeTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Scripts/ScriptTriggerAttributeTests.cs
new file mode 100644
index 00000000..2b2d14f2
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Scripts/ScriptTriggerAttributeTests.cs
@@ -0,0 +1,46 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Scripts;
+
+/// Flow Debugger script entry (design 2026-09-08 §2): the monitored attribute of a ValueChange/Conditional trigger.
+public class ScriptTriggerAttributeTests
+{
+ [Theory]
+ [InlineData("ValueChange", """{"attributeName":"Level"}""", "Level")]
+ [InlineData("valuechange", """{"attribute":"Level"}""", "Level")]
+ [InlineData("Conditional", """{"attributeName":"Level","operator":">","threshold":"5"}""", "Level")]
+ [InlineData("CONDITIONAL", """{"attribute":"Flag"}""", "Flag")]
+ public void WriteKinds_ReturnTheMonitoredAttribute(string kind, string config, string expected)
+ {
+ Assert.True(ScriptTriggerAttribute.TryRead(kind, config, out var attribute));
+ Assert.Equal(expected, attribute);
+ Assert.True(ScriptTriggerAttribute.IsWriteKind(kind));
+ }
+
+ [Theory]
+ [InlineData("Call", """{"attributeName":"Level"}""")]
+ [InlineData("Interval", """{"intervalMs":1000}""")]
+ [InlineData("Expression", """{"expression":"true"}""")]
+ [InlineData(null, null)]
+ [InlineData("", "")]
+ public void OtherKinds_ReturnFalse_EvenWhenTheConfigNamesAnAttribute(string? kind, string? config)
+ {
+ Assert.False(ScriptTriggerAttribute.TryRead(kind, config, out var attribute));
+ Assert.Null(attribute);
+ Assert.False(ScriptTriggerAttribute.IsWriteKind(kind));
+ }
+
+ [Theory]
+ [InlineData("ValueChange", null)]
+ [InlineData("ValueChange", "")]
+ [InlineData("ValueChange", "not json")]
+ [InlineData("ValueChange", "[1,2]")]
+ [InlineData("ValueChange", """{"attributeName":""}""")]
+ [InlineData("ValueChange", """{"other":"x"}""")]
+ public void WriteKind_WithoutAReadableAttribute_ReturnsFalse_ButIsStillAWriteKind(string kind, string? config)
+ {
+ Assert.False(ScriptTriggerAttribute.TryRead(kind, config, out var attribute));
+ Assert.Null(attribute);
+ Assert.True(ScriptTriggerAttribute.IsWriteKind(kind));
+ }
+}