diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Configuration/MeshRegistrationRoleScopingTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Configuration/MeshRegistrationRoleScopingTests.cs
new file mode 100644
index 00000000..e9e16a4f
--- /dev/null
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Configuration/MeshRegistrationRoleScopingTests.cs
@@ -0,0 +1,169 @@
+using Shouldly;
+using Xunit;
+
+namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Configuration;
+
+///
+/// Security register item 3 (DI role-scoping pin). IMeshPskProvider and
+/// IMeshCommandDialer are central's per-cluster key lookup + outbound command dialer —
+/// a driver-only node holds exactly its own MeshTransport:GrpcPsk and never dials
+/// another node, so these two singletons must be registered ONLY inside one of Program.cs's
+/// if (hasAdmin) blocks. Nothing in the language enforces that placement; this is a
+/// source scan so a refactor that hoists either registration out of its block fails loudly
+/// here instead of silently widening the mesh trust boundary to driver-only nodes.
+///
+///
+/// Program.cs gates several unrelated concerns behind repeated if (hasAdmin) checks
+/// (auth/AdminUI mounting, health probes, gRPC endpoint mapping, ...), so this pin cannot
+/// assume there is only one such block in the file — it must find the SPECIFIC block that
+/// contains each registration and prove that containment is unambiguous.
+///
+[Trait("Category", "Unit")]
+public sealed class MeshRegistrationRoleScopingTests
+{
+ private const string PskRegistration =
+ "AddSingleton";
+
+ private const string DialerRegistration =
+ "AddSingleton";
+
+ private const string HasAdminMarker = "if (hasAdmin)";
+
+ private static string ReadProgramSource()
+ {
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
+ while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx")))
+ dir = dir.Parent;
+ dir.ShouldNotBeNull();
+
+ return File.ReadAllText(Path.Combine(
+ dir!.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.Host", "Program.cs"));
+ }
+
+ private static int CountOccurrences(string haystack, string needle)
+ {
+ var count = 0;
+ var index = 0;
+ while ((index = haystack.IndexOf(needle, index, StringComparison.Ordinal)) >= 0)
+ {
+ count++;
+ index += needle.Length;
+ }
+
+ return count;
+ }
+
+ ///
+ /// Brace-matches every if (hasAdmin) block in the file (not a regex over the block
+ /// body — the body is free-form C# and a regex would be fragile to reformatting) and
+ /// returns each block's (open-brace, close-brace) character range.
+ ///
+ private static List<(int Start, int End)> FindHasAdminBlockRanges(string source)
+ {
+ var ranges = new List<(int Start, int End)>();
+ var searchIndex = 0;
+ while (true)
+ {
+ var markerIndex = source.IndexOf(HasAdminMarker, searchIndex, StringComparison.Ordinal);
+ if (markerIndex < 0)
+ break;
+
+ var openBraceIndex = source.IndexOf('{', markerIndex + HasAdminMarker.Length);
+ openBraceIndex.ShouldBeGreaterThan(-1,
+ $"'{HasAdminMarker}' at offset {markerIndex} is not followed by an opening brace.");
+
+ var depth = 0;
+ var closeBraceIndex = -1;
+ for (var i = openBraceIndex; i < source.Length; i++)
+ {
+ if (source[i] == '{')
+ depth++;
+ else if (source[i] == '}')
+ {
+ depth--;
+ if (depth == 0)
+ {
+ closeBraceIndex = i;
+ break;
+ }
+ }
+ }
+
+ closeBraceIndex.ShouldBeGreaterThan(-1,
+ $"brace-matching for the '{HasAdminMarker}' block starting at offset {markerIndex} never closed.");
+
+ ranges.Add((openBraceIndex, closeBraceIndex));
+ searchIndex = closeBraceIndex + 1;
+ }
+
+ return ranges;
+ }
+
+ ///
+ /// Locates the single if (hasAdmin) block that contains .
+ /// Fails loudly — rather than silently picking the first match — if the registration is
+ /// missing entirely, sits outside every admin-role block, or (should a future edit make two
+ /// blocks both contain matching text) the containing block is ambiguous. Any of those is a
+ /// reason to stop and re-examine the pin, not to guess.
+ ///
+ private static string ExtractContainingHasAdminBlock(string source, string registration, string whyItMatters)
+ {
+ var ranges = FindHasAdminBlockRanges(source);
+ ranges.ShouldNotBeEmpty(
+ $"Program.cs no longer contains any '{HasAdminMarker}' block — the admin-role gate this " +
+ $"pin relies on is gone. {whyItMatters}");
+
+ var registrationIndex = source.IndexOf(registration, StringComparison.Ordinal);
+ registrationIndex.ShouldBeGreaterThan(-1,
+ $"'{registration}' was not found anywhere in Program.cs. {whyItMatters}");
+
+ var containingBlocks = ranges
+ .Where(r => registrationIndex >= r.Start && registrationIndex <= r.End)
+ .ToList();
+
+ containingBlocks.ShouldNotBeEmpty(
+ $"'{registration}' is registered OUTSIDE every '{HasAdminMarker}' block. {whyItMatters}");
+
+ containingBlocks.Count.ShouldBe(1,
+ $"'{registration}' falls inside {containingBlocks.Count} '{HasAdminMarker}' blocks, so " +
+ "which one actually gates it is ambiguous — extend this pin to disambiguate rather than " +
+ $"trusting the first match. {whyItMatters}");
+
+ var (start, end) = containingBlocks[0];
+ return source.Substring(start, end - start + 1);
+ }
+
+ [Fact]
+ public void MeshPskProvider_IsRegisteredExactlyOnce_InsideAnUnambiguousHasAdminBlock()
+ {
+ const string why =
+ "A driver-only node must not carry central's per-cluster key lookup, which it can never " +
+ "legitimately use.";
+ var source = ReadProgramSource();
+
+ // Global uniqueness first: if this text appeared twice, block-containment below could pass
+ // even though a second, unguarded copy exists elsewhere in the file.
+ CountOccurrences(source, PskRegistration).ShouldBe(1,
+ $"IMeshPskProvider must be registered exactly once in Program.cs. {why}");
+
+ var block = ExtractContainingHasAdminBlock(source, PskRegistration, why);
+ block.ShouldContain(PskRegistration, Case.Sensitive,
+ $"IMeshPskProvider must be registered inside 'if (hasAdmin)'. {why}");
+ }
+
+ [Fact]
+ public void MeshGrpcCommandDialer_IsRegisteredExactlyOnce_InsideAnUnambiguousHasAdminBlock()
+ {
+ const string why =
+ "A driver-only node never dials another node and must not carry central's outbound mesh " +
+ "command dialer.";
+ var source = ReadProgramSource();
+
+ CountOccurrences(source, DialerRegistration).ShouldBe(1,
+ $"IMeshCommandDialer must be registered exactly once in Program.cs. {why}");
+
+ var block = ExtractContainingHasAdminBlock(source, DialerRegistration, why);
+ block.ShouldContain(DialerRegistration, Case.Sensitive,
+ $"IMeshCommandDialer must be registered inside 'if (hasAdmin)'. {why}");
+ }
+}