Files
justme ad5efc62ff agent: unify listening and dialing with directed mutual measurements
Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry.

Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
2026-10-21 11:55:36 +08:00

92 lines
3.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Fleet.Core;
namespace MoShiro.Protocol;
/// <summary>Task = { id, scope, kind, target, params, schedule, enabled }。参数已经按 ParamSpec 校验并补全默认值。</summary>
public sealed class TaskSpec
{
[JsonPropertyName("id")]
public TaskId Id { get; set; }
/// <summary>line | target</summary>
[JsonPropertyName("scope")]
public string Scope { get; set; } = "line";
[JsonPropertyName("kind")]
public string Kind { get; set; } = "";
[JsonPropertyName("target")]
public string? Target { get; set; }
[JsonPropertyName("params")]
public Dictionary<string, JsonElement> Params { get; set; } = [];
/// <summary>continuous | every:&lt;分钟&gt; | once</summary>
[JsonPropertyName("schedule")]
public string Schedule { get; set; } = TaskSchedule.Continuous;
[JsonPropertyName("enabled")]
public bool Enabled { get; set; } = true;
}
public sealed class PeerSpec
{
[JsonPropertyName("node_id")]
public NodeId NodeId { get; set; }
/// <summary>拨号方的身份公钥(SPKI,base64)。</summary>
[JsonPropertyName("pubkey")]
public string PublicKey { get; set; } = "";
}
/// <summary>期望状态文档(Fleet state type = <c>task_set</c>):Backend 为每个 Agent 计算的有效任务集。</summary>
public sealed class TaskSet
{
public const string StateType = "task_set";
[JsonPropertyName("version")]
public long Version { get; set; }
[JsonPropertyName("tasks")]
public List<TaskSpec> Tasks { get; set; } = [];
/// <summary>允许拨入这个节点的拨号方公钥。</summary>
[JsonPropertyName("peers")]
public List<PeerSpec> Peers { get; set; } = [];
/// <summary>操作员集合的链(passkey 签的,创世在前):节点从固定的创世指纹验过才采用。</summary>
[JsonPropertyName("trust_chain")]
public List<SignedStatementDto>? TrustChain { get; set; }
}
public static class TaskSchedule
{
public const string Continuous = "continuous";
public const string Once = "once";
public const string EveryPrefix = "every:";
public static string Every(int minutes) => EveryPrefix + minutes.ToString(CultureInfo.InvariantCulture);
/// <summary>校验调度字符串:<c>every:N</c> 的 N ≥ 60(适用于重负载 Kind)。</summary>
public static Result<int?> Validate(string schedule)
{
if (schedule is Continuous or Once)
{
return Result<int?>.Ok(null);
}
if (schedule.StartsWith(EveryPrefix, StringComparison.Ordinal)
&& int.TryParse(schedule.AsSpan(EveryPrefix.Length), NumberStyles.None, CultureInfo.InvariantCulture, out int minutes))
{
return minutes >= 60
? Result<int?>.Ok(minutes)
: Result<int?>.Fail(MoShiroErrors.ValidationFailed, "every:N requires N >= 60 minutes");
}
return Result<int?>.Fail(MoShiroErrors.ValidationFailed, "schedule must be continuous, once or every:<minutes>");
}
}