99 lines
3.5 KiB
C#
99 lines
3.5 KiB
C#
using System.Text.Json;
|
|
using Fleet.Core;
|
|
using Fleet.Rpc;
|
|
|
|
namespace Fleet.Server;
|
|
|
|
public sealed record EnrollmentContext(string? RemoteIp, JsonElement? ProductMeta);
|
|
|
|
/// <summary>
|
|
/// 服务器的身份密钥:操作员用 passkey 把它的公钥签进操作员集合。有它的时候,认证挑战会带上服务器的签名,节点据此确认对面是真的服务器,
|
|
/// 并在这条链路上用应用层加密。没有注册就是旧的行为(没有服务器身份)。
|
|
/// </summary>
|
|
public interface IServerIdentity
|
|
{
|
|
byte[] PublicKeySpki { get; }
|
|
|
|
/// <summary>对一段内容签名(<c>Fleet.Identity.ServerHello.Payload</c> 给出的)。</summary>
|
|
byte[] Sign(ReadOnlySpan<byte> payload);
|
|
}
|
|
|
|
/// <summary>操作员集合的链(passkey 签的,创世在前)。没有初始化信任根时是空的。节点注册时用它确认「这个面板确实是我的操作员的」。</summary>
|
|
/// <summary>节点握手时自述的二进制(旧节点没有)。</summary>
|
|
public sealed record BinaryEvidence(string? Sha256, string? Manifest, string? Signature);
|
|
|
|
/// <summary>是否让这个节点继续:不放行时 <c>Reason</c> 会原样告诉节点(这是它自己的信息,不是秘密)。</summary>
|
|
public sealed record BinaryDecision(bool Accept, string Reason = "");
|
|
|
|
/// <summary>
|
|
/// 服务端对节点二进制的把关:节点第一次握手成功的那一刻绑定它的二进制哈希,之后哈希对不上、又不在 CI 签名的更新清单里,就不让它继续运行。
|
|
/// 没有注册实现时不做任何检查。
|
|
/// </summary>
|
|
public interface IBinaryAttestor
|
|
{
|
|
ValueTask<BinaryDecision> CheckAsync(NodeRecord node, BinaryEvidence evidence, CancellationToken ct);
|
|
}
|
|
|
|
public interface IOperatorTrustSource
|
|
{
|
|
ValueTask<IReadOnlyList<SignedStatementDto>> ChainAsync(CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>产品挂钩:节点注册成功之后调用。</summary>
|
|
public interface IEnrollmentHook
|
|
{
|
|
ValueTask OnEnrolledAsync(NodeRecord node, EnrollmentContext context, CancellationToken ct);
|
|
}
|
|
|
|
public enum NodeEventKind
|
|
{
|
|
Online,
|
|
Offline,
|
|
Hello,
|
|
Heartbeat,
|
|
}
|
|
|
|
public sealed record NodeEvent(NodeEventKind Kind, NodeRecord Node, long AtMs, HelloParams? Hello = null, JsonElement? Extra = null);
|
|
|
|
public interface INodeEventSink
|
|
{
|
|
ValueTask OnEventAsync(NodeEvent nodeEvent, CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>期望状态提供者:为每个节点计算期望状态的完整快照(天然幂等)。</summary>
|
|
public interface IStateProvider
|
|
{
|
|
/// <summary>状态文档类型,例如 <c>task_set</c>。</summary>
|
|
string Type { get; }
|
|
|
|
ValueTask<StateDocument?> GetAsync(NodeRecord node, CancellationToken ct);
|
|
|
|
/// <summary>节点应用之后回调,<c>rejected[]</c> 带结构化原因。</summary>
|
|
ValueTask OnAppliedAsync(NodeRecord node, long version, StateSyncResult result, CancellationToken ct);
|
|
}
|
|
|
|
public sealed record StateDocument(long Version, JsonElement Doc);
|
|
|
|
public enum InboxOutcome
|
|
{
|
|
Stored,
|
|
Duplicate,
|
|
Rejected,
|
|
}
|
|
|
|
/// <summary>收件箱:持久化之后才返回 Stored,框架随后才回复 ACK。</summary>
|
|
public interface IInbox
|
|
{
|
|
byte Channel { get; }
|
|
|
|
ValueTask<InboxOutcome> HandleAsync(NodeRecord node, Guid msgId, ReadOnlyMemory<byte> payload, CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>通知 Fleet:某个节点的期望状态变了,需要重新计算并推送。</summary>
|
|
public interface IStateNotifier
|
|
{
|
|
void NotifyChanged(NodeId node);
|
|
|
|
void NotifyAllChanged();
|
|
}
|