# SRV 候选选择契约

状态：2026-11-01，0.2.0，发布执行记录见 release-0.2.0.md。

依据 [RFC 6763 §5](https://www.rfc-editor.org/rfc/rfc6763.html#section-5) 与 [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782.html)。这是浏览器的端点候选顺序，不是连接器；应用仍负责连接、认证、超时，以及失败后尝试下一候选。

## 输入与顺序

1. 使用同一 scope、实例和 IN/SRV RRSet 中所有未过期记录，支持候选分布在多个响应包中。缓存按 [RFC 6762 §10.2](https://www.rfc-editor.org/rfc/rfc6762.html#section-10.2) 处理 cache-flush：旧记录在一秒宽限后移除，后续分包可刷新并救回；最近一秒接收的成员不会被另一分包立即驱逐。候选不局限于最后一包。
2. 优先级按数值从小到大排列；当前组全部排列后才进入下一优先级。
3. 同组先做固定种子随机排列，将零权重项放在前面，再按 RFC 的闭区间 `[0, 权重和]` 抽票，选中后移除并重复。全部权重为零时仍通过初始随机排列分散首选；零与非零混合时零权重保留很小的入选机会。
4. 伪随机流独立于查询抖动，以 Engine 初始 seed、scope、规范化实例名与排序后的候选字段确定。报文内记录顺序、TTL 刷新和无关查询不改变同一候选集的排序；它不是密码学随机数。
5. 优先候选的地址缺失时保持部分状态，不因其他候选已有地址而悄悄越过优先级。所有候选目标都会进入有界地址查询任务；调用方可根据实际连接结果依序尝试。

## 公开模型与兼容性

`ServiceSnapshot.srv_candidates` 为按上述顺序排列的 `Array[SrvCandidate]`。每项保留 `priority`、`weight`、`port`、`target` 及该目标当前缓存中的 `addresses`。平面 `target` / `port` / `addresses` 对应首选候选；根目标或端口 0 保持不可用状态。TXT 多记录冲突继续保守处理。

事件和快照在数组边界复制候选与地址，调用方修改返回数组不影响引擎。候选字段或候选地址变化属于内容更新；单纯剩余 TTL 变化不产生 Updated。端口/主机变化、地址丢失与恢复遵循原有 Updated / Unavailable / Resolved 契约。

端口或目标替换时，旧候选可能在 cache-flush / goodbye 宽限期内仍可见，平面字段按当前候选集重新选择。应用不应把最新到达的一条 SRV 当成完整替换通知；应处理 Updated 并在连接失败时尝试下一候选。TXT 的最新批次与冲突策略独立，不随 SRV 的 RRSet 规则改变。

JSON 新增 `service.srv_candidates`，其余字段保持原义。旧版实网证据文件不改写；CLI/浏览器回归投影原字段与 0.1.0 报告比较，并另行检查新候选字段。当前 API 扩展属于 0.2.0，不回填到 0.1.0。

公开 DTO 接纳也校验 A/AAAA 长度、SRV 无符号 16 位范围与类型/数据对应。无效受支持 IN 记录导致整包拒绝，缓存、时钟和旧快照不被半更新。

## 针对性验收

- 不同优先级、多候选保留、候选地址隔离和返回数组复制。
- 1024 个固定种子的宽松权重回归、零权重混合、全零、多优先级、最大字段、空集与单条。
- 同一记录集换序及重复刷新不改变首选或制造内容事件。
- 备用主机地址查询、显式不可用 SRV 与恢复。
- 跨两个响应包保留数值优先候选、flush 宽限边界、后续分包救回、重复刷新去重及 goodbye 删除。

这些是确定性核心检查，不替代独立实现的 SRV 实网验收，也不构成统计性能保证。
