feat: declare every protocol method's direction and enforce it in the router; add the zero-trust design [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

Methods, roles and binary channels are declared in one table per plane (node and peer). RpcRouter checks direction, kind and role when handlers are registered, when messages arrive (fleet_protocol_violation, disconnect after repeated violations) and before sending. NodeConnection is split by responsibility.
This commit is contained in:
justme committed 2026-10-07 16:17:02 +08:00
1 parent ee22309e65
commit 1ff847e45a
36 files changed
+1799 -639

No files matched your search

@@ -67,7 +67,7 @@ public sealed class IdentityAndRpcTests
[Fact]
public async Task Router_DispatchesTypedRequests_AndReportsStructuredErrors()
{
var router = new RpcRouter<string>()
var router = new RpcRouter<string>(RpcSide.Listener, TestTables.Of(TestTables.Req("test.add"), TestTables.Req("test.boom"), TestTables.Req("test.nope")))
.On("test.add", EchoContext.Default.AddParams, EchoContext.Default.AddResult,
(caller, p, _) => ValueTask.FromResult(p.A < 0 ? Result<AddResult>.Fail(FleetErrors.Forbidden, "negative") : Result<AddResult>.Ok(new AddResult { Sum = p.A + p.B, Caller = caller })))
.On<AddParams, AddResult>("test.boom", EchoContext.Default.AddParams, EchoContext.Default.AddResult, (_, _, _) => throw new InvalidOperationException("secret detail"));
@@ -92,7 +92,7 @@ public sealed class IdentityAndRpcTests
public async Task Router_HandlerExceptions_AreContained_AndNeverLeakDetails()
{
string? logged = null;
var router = new RpcRouter<string>((method, ex) => logged = method + ":" + ex.Message)
var router = new RpcRouter<string>(RpcSide.Listener, TestTables.Of(TestTables.Req("test.boom")), onUnhandled: (method, ex) => logged = method + ":" + ex.Message)
.On<AddParams, AddResult>("test.boom", EchoContext.Default.AddParams, EchoContext.Default.AddResult, (_, _, _) => throw new InvalidOperationException("secret detail"));
RpcMessage reply = (await router.DispatchAsync("a", Request(1, "test.boom", """{"a":1,"b":1}"""), CancellationToken.None))!;
@@ -105,7 +105,7 @@ public sealed class IdentityAndRpcTests
public async Task Router_Notifications_ProduceNoResponse_EvenOnFailure()
{
int calls = 0;
var router = new RpcRouter<string>().OnNotify("test.note", EchoContext.Default.AddParams, (_, _, _) =>
var router = new RpcRouter<string>(RpcSide.Listener, TestTables.Of(TestTables.Note("test.note"), TestTables.Note("test.unknown"))).OnNotify("test.note", EchoContext.Default.AddParams, (_, _, _) =>
{
calls++;
return ValueTask.CompletedTask;