diff --git a/docs/plans/2026-09-10-handshake-reset-echo-design.md b/docs/plans/2026-09-10-handshake-reset-echo-design.md
index 4d008f04..3ccff0f0 100644
--- a/docs/plans/2026-09-10-handshake-reset-echo-design.md
+++ b/docs/plans/2026-09-10-handshake-reset-echo-design.md
@@ -80,6 +80,10 @@ public readonly record struct HandshakeResult(
HandshakePhase Phase, string? ErrorMessage);
```
+`ResetFailed` (`!Matched && Phase == Reset`) is a computed convenience: script bodies import only
+`System`, `System.Collections.Generic`, `System.Linq` and `System.Threading.Tasks`, so a script
+branching on the enum would have to spell `ZB.MOM.WW.ScadaBridge.Commons.Types.HandshakePhase`.
+
Semantics of the new overload, in execution order:
1. **Reset write** — `resetKey = resetValue` through the existing `SetAttribute` path (static or
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs
new file mode 100644
index 00000000..1698b389
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs
@@ -0,0 +1,47 @@
+namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
+
+///
+/// Which leg of a reset-aware Attributes.WriteBatchAndWaitAsync handshake a
+/// describes (Gitea #36, design 2026-09-10 §4).
+///
+public enum HandshakePhase
+{
+ /// The reset write + reset-echo wait that precedes the batch and trigger.
+ Reset,
+
+ /// The batch + trigger write and the response wait that follow an observed reset echo.
+ Response,
+}
+
+///
+/// Result of the reset-aware Attributes.WriteBatchAndWaitAsync overload (Gitea #36):
+/// like , plus the the outcome belongs to so a
+/// script can report "timed out waiting for the reset echo" separately from "timed out
+/// waiting for the device to complete". A reset that is never echoed means the batch and
+/// trigger were NEVER written.
+///
+///
+/// when the response attribute reached its target within the timeout
+/// (which implies the reset echo was observed first).
+///
+/// The matched value of the phase's attribute; on timeout / error.
+/// The attribute quality at match time; on the non-match paths.
+/// when the phase's wait timed out before a match.
+/// The leg this result describes — only when that leg failed.
+/// Why the phase did not match, when known; on success.
+public readonly record struct HandshakeResult(
+ bool Matched,
+ object? Value,
+ string? Quality,
+ bool TimedOut,
+ HandshakePhase Phase,
+ string? ErrorMessage)
+{
+ ///
+ /// when the reset leg did not match (timeout or error), i.e. the
+ /// device never echoed the reset value and nothing else was written. Provided so a
+ /// script can branch without spelling the type name, which
+ /// is not in the script imports.
+ ///
+ public bool ResetFailed => !Matched && Phase == HandshakePhase.Reset;
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Types/HandshakeResultTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Types/HandshakeResultTests.cs
new file mode 100644
index 00000000..c6427e13
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Types/HandshakeResultTests.cs
@@ -0,0 +1,23 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Types;
+
+namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Types;
+
+///
+/// Gitea #36: lets a script tell WHICH leg of a reset-aware
+/// handshake failed without naming the enum (script bodies
+/// import only System/System.Collections.Generic/System.Linq/System.Threading.Tasks).
+///
+public class HandshakeResultTests
+{
+ [Fact]
+ public void ResetFailed_IsTrue_OnlyForAnUnmatchedResetPhase()
+ {
+ var resetTimeout = new HandshakeResult(false, null, null, true, HandshakePhase.Reset, "reset timed out");
+ var responseTimeout = new HandshakeResult(false, null, null, true, HandshakePhase.Response, "response timed out");
+ var matched = new HandshakeResult(true, true, "Good", false, HandshakePhase.Response, null);
+
+ Assert.True(resetTimeout.ResetFailed);
+ Assert.False(responseTimeout.ResetFailed);
+ Assert.False(matched.ResetFailed);
+ }
+}