docs(protocol): require facade boundary in runtime spec

This commit is contained in:
liu weikai committed 2026-06-14 10:26:49 +08:00
1 parent 4a15a787ff
commit de1b1eb080
1 file changed
+202 -21
@@ -48,6 +48,32 @@ Status date: 2026-06-14
本文只采用少数 GoF 23 设计模式作为主轴,避免“模式堆砌”。
### Design Verdict
本规格接受原始改造设想作为约束基线:Trail Mate 的协议层改造不是把若干 helper
抽到 shared 目录,而是建立一条清晰的用例入口到平台执行链路:
```text
UI / ChatService
-> MeshProtocolFacade
-> ProtocolIntent
-> IProtocolRuntime strategy
-> Protocol State
-> ProtocolEffects
-> IProtocolEffectExecutor bridge
-> Platform adapters
```
其中 `Strategy + Command + State + Bridge + Adapter` 是主轴。如果这五个没有立住,
`Visitor / Chain of Responsibility / Facade / Factory / Builder` 都只能算局部工具,不能宣称
协议 runtime 改造完成。
`Facade` 虽然不是前五个主轴之一,但它是上层用例入口边界,不能只存在于图里。规格中出现
`MeshProtocolFacade` 时,表示必须有代码级对象或等价命名的 concrete boundary。该对象负责给
UI / ChatService 暴露稳定用例 API,并隐藏 runtime 选择、effect 执行、TX 失败回灌和 action
result 投影的编排细节。若未来决定不用 `MeshProtocolFacade` 这个名字,必须先修改本规格,
明确新的等价代码对象和验收条件。
### Language Baseline
目标方向是现代 C++,长期可以提升到 C++20;但当前 ESP32 / nRF52 的 PlatformIO
@@ -77,8 +103,8 @@ C++20,并考虑引入 concepts / `std::span` / 更强类型约束。
| --- | --- | --- |
| Visitor | `std::variant<ProtocolEffect...>` + `std::visit` 的 effect dispatch | 只用于 executor/test 处理 effects,不扩散到 UI |
| Chain of Responsibility | incoming packet 分类管线:decrypt -> routing -> nodeinfo -> position -> trace -> text/appdata | handler 顺序必须写进 runtime 或 spec,不能形成隐式吞包黑洞 |
| Facade | `MeshProtocolFacade` 给 UI/ChatService 的入口 | 只做用例门面,不沉淀协议语义 |
| Abstract Factory / Factory Method | product composition 选择 runtime + executor + codec | 用静态对象/引用也可以,不强制动态分配 |
| Facade | `MeshProtocolFacade` 给 UI/ChatService 的入口 | 必须落成真实代码边界;只做用例门面,不沉淀协议语义 |
| Abstract Factory / Factory Method | product composition 选择 facade + runtime + executor + codec | 用静态对象/引用也可以,不强制动态分配 |
| Builder | packet/control payload 构造 | 局部用于 codec/build request,不能替代 runtime |
### Patterns To Avoid As Main Axis
@@ -118,6 +144,42 @@ Runtime 是协议真相所在:
- `MeshCoreRuntime` 解释 MeshCore Intent、incoming frame、trace path、NodeInfo control、tick;
- runtime 可以拥有 State,但不能拥有平台 IO。
### Facade
Facade 是 UI / ChatService 面对协议系统的稳定用例入口。它不是 protocol runtime 的别名,也不是
adapter 的包装名。
`MeshProtocolFacade` 必须拥有或聚合:
- 当前协议的 `IProtocolRuntime` strategy;
- 对应平台的 `IProtocolEffectExecutor` bridge;
- 构造 `RuntimeContext` 所需的 clock / self node / protocol facts provider;
- 将 `ProtocolEffects` 执行、记录、回灌 `TxResult`、并向上层返回 app-facing result 的编排逻辑。
Facade 必须暴露的是用例动作,而不是 wire 细节:
- `startTraceRoute(peer)`
- `exchangePosition(peer)`
- `requestNodeInfo(peer, wantResponse)`
- `sendText(channel, peer, text)`
- `sharePosition(...)`
- `shareWaypoint(...)`
- `handleIncoming(packet)`
- `handleTxResult(result)`
- `tick(nowMs)`
Facade 不得:
- 直接选择 Meshtastic portnum 或 MeshCore payload type;
- 编码 protobuf / MeshCore frame;
- 决定 PKI resync、NodeInfo reply、TraceRoute completion、ACK timeout 等协议语义;
- 执行 radio/storage/BLE 细节;
- 变成新的 God Object 或 Mediator。
Facade 的职责是隔离上层与协议编排。上层不应该直接拼 `ProtocolIntent` 后调用 runtime,
也不应该直接遍历 `ProtocolEffects` 后调用 executor。过渡期允许少量平台 adapter 仍然这样做,
但每个 active UI / ChatService 入口必须有迁入 `MeshProtocolFacade` 的计划和测试。
### Effect
Effect 是 runtime 要求外部世界发生的动作:
@@ -154,6 +216,64 @@ Codec 只负责编解码:
- `MeshCoreCodec`:frame/header/direct data/group data/control/trace;
- codec 可以使用 Builder 风格的 build request,但不得保存业务状态。
### Product Composition / Factory
产品组合阶段负责把 `MeshProtocolFacade`、`IProtocolRuntime`、`IProtocolEffectExecutor`、codec 和平台
facts provider 接起来。这里使用 Abstract Factory / Factory Method 的意图是避免每个 UI 或 adapter
手动拼装协议对象。
嵌入式平台不要求动态分配。允许使用静态对象和返回引用的工厂:
```cpp
struct ProtocolRuntimeBundle
{
MeshProtocolFacade* facade;
IProtocolRuntime* runtime;
IProtocolEffectExecutor* executor;
};
ProtocolRuntimeBundle protocolRuntimeFor(MeshProtocol protocol);
```
Factory 可以知道产品/平台能力,但不得决定协议语义。它选择“用哪套 runtime/executor”,不决定
“该不该回复 NodeInfo”或“TraceRoute 何时完成”。
### Incoming Handler Chain
Incoming packet 的分类可以局部使用 Chain of Responsibility,但顺序必须显式,不能形成隐式吞包。
handler 可以解密、分类、更新状态、产生 effects,但必须使用统一返回语义:
```cpp
enum class PacketHandling
{
NotHandled,
HandledContinue,
HandledStop,
DropWithEffects,
};
```
Meshtastic incoming chain 的概念顺序必须是:
1. wire/Data 解码与 channel / PKI 解密事实建立;
2. routing app / routing error;
3. NodeInfo;
4. Position;
5. TraceRoute;
6. KeyVerification;
7. text / generic app-data projection。
MeshCore incoming chain 的概念顺序必须是:
1. frame/header/direct/group 解码与 peer identity facts 建立;
2. ACK;
3. control: discover / NodeInfo;
4. trace;
5. text / app-data projection。
这些 handler 可以先以函数存在,也可以之后拆成对象;但顺序和吞包语义必须属于 runtime/codec
边界,不能散落在平台 adapter 的任意 if 分支里。
## Primary Class Model
```mermaid
@@ -163,8 +283,11 @@ classDiagram
+exchangePosition(NodeId peer)
+requestNodeInfo(NodeId peer, bool wantResponse)
+sendText(ChannelId channel, NodeId peer, string text)
+onIncomingBytes(bytes)
+tick(nowMs)
+sharePosition(PositionSnapshot position)
+shareWaypoint(WaypointSnapshot waypoint)
+handleIncoming(IncomingPacket packet)
+handleTxResult(TxResult result)
+tick(uint32_t nowMs)
}
class IProtocolRuntime {
@@ -204,9 +327,16 @@ classDiagram
class NrfProtocolExecutor
class RecordingProtocolExecutor
class ProtocolRuntimeFactory {
+protocolRuntimeFor(MeshProtocol protocol) ProtocolRuntimeBundle
}
class MeshtasticCodec
class MeshCoreCodec
ProtocolRuntimeFactory ..> MeshProtocolFacade
ProtocolRuntimeFactory ..> IProtocolRuntime
ProtocolRuntimeFactory ..> IProtocolEffectExecutor
MeshProtocolFacade o-- IProtocolRuntime
MeshProtocolFacade o-- IProtocolEffectExecutor
IProtocolRuntime <|.. MeshtasticRuntime
@@ -225,8 +355,11 @@ classDiagram
```mermaid
flowchart LR
UI[UI / ChatService] -->|Command: ProtocolIntent| Facade[MeshProtocolFacade]
Facade -->|Strategy dispatch| Runtime[IProtocolRuntime]
UI[UI / ChatService] -->|Use-case command| Facade[MeshProtocolFacade]
PlatformIn[Platform packet source] -->|IncomingPacket / TxResult / tick| Facade
Facade -->|build ProtocolIntent| Intent[ProtocolIntent]
Intent -->|Strategy dispatch| Runtime[IProtocolRuntime]
Facade -->|direct incoming / tx / tick| Runtime
Runtime --> MT[MeshtasticRuntime]
Runtime --> MC[MeshCoreRuntime]
MT -->|State| MTState[ACK / TraceRoute / Position / PKI states]
@@ -235,7 +368,8 @@ flowchart LR
MC -->|Codec dependency| MCCodec[MeshCoreCodec]
MT -->|Effects| Effects[ProtocolEffects]
MC -->|Effects| Effects
Effects -->|Visitor dispatch| Executor[IProtocolEffectExecutor]
Effects -->|returned to Facade| Facade
Facade -->|Visitor dispatch| Executor[IProtocolEffectExecutor]
Executor -->|Bridge implementation| Esp[ESP executor]
Executor -->|Bridge implementation| Nrf[nRF executor]
Esp -->|Adapter| EspIO[ESP radio / storage / BLE]
@@ -258,6 +392,7 @@ sequenceDiagram
participant Platform as Platform IO
User->>Facade: startTraceRoute(peer)
Facade->>Facade: build TraceRouteIntent + RuntimeContext
Facade->>Runtime: prepareOutgoing(TraceRouteIntent)
Runtime->>State: allocate request id and enter pending state
Runtime-->>Facade: ProtocolEffects(SendPacket/SendTraceRoute)
@@ -267,7 +402,13 @@ sequenceDiagram
Codec-->>Executor: EncodedPacket
Executor->>Platform: radio/storage/event operation
Platform-->>Executor: ok/fail
Executor-->>Facade: EffectExecutionResult
end
opt tx failure / tx metadata
Facade->>Runtime: handleTxResult(TxResult)
Runtime-->>Facade: ProtocolEffects(EmitActionResultEffect...)
end
Facade-->>User: ActionHandle / app-facing result
```
### Incoming Packet
@@ -276,15 +417,17 @@ sequenceDiagram
sequenceDiagram
autonumber
participant Platform as Platform IO
participant Executor as Platform Adapter
participant Adapter as Platform Adapter
participant Facade as MeshProtocolFacade
participant Runtime as IProtocolRuntime
participant Chain as Incoming Handler Chain
participant State as Protocol State
participant Facade as MeshProtocolFacade
participant UI as UI/ChatService
Platform->>Executor: raw packet/frame
Executor->>Runtime: handleIncoming(IncomingPacket)
Platform->>Adapter: raw packet/frame
Adapter->>Adapter: decode/decrypt enough to form IncomingPacket facts
Adapter->>Facade: handleIncoming(IncomingPacket)
Facade->>Runtime: handleIncoming(IncomingPacket)
Runtime->>Chain: decrypt/classify/route handlers
alt NodeInfo request
Chain->>State: apply NodeInfo rule
@@ -296,8 +439,8 @@ sequenceDiagram
Chain->>State: complete trace action
State-->>Runtime: EmitActionResultEffect
end
Runtime-->>Executor: ProtocolEffects
Executor-->>Facade: publish text/data/action result effects
Runtime-->>Facade: ProtocolEffects
Facade->>Adapter: execute platform effects through executor
Facade-->>UI: stable app-facing result
```
@@ -479,19 +622,55 @@ This split intentionally leaves direct ACK burst scheduling and multi-ACK frame
radio execution details. The question "is this local send still pending, completed, failed, or timed out" belongs to
`MeshCoreRuntime`.
## Spec Conformance Criteria
不能只因为 runtime helper 被抽出来就宣称改造完成。满足本规格必须同时满足以下条件:
1. `IProtocolRuntime`、`MeshtasticRuntime`、`MeshCoreRuntime` 是真实代码对象,并有 shared tests 覆盖
outgoing / incoming / tx result / tick。
2. `ProtocolIntent` 是 UI / usecase 进入协议系统的命令模型;active UI / ChatService 入口不得直接制造
portnum、payload type、protobuf bytes 或 MeshCore control frame。
3. 跨时间协议会话必须由 runtime state 拥有。UI 和 adapter 不得独立维护 TraceRoute / Position
exchange / PKI resync / ACK completion 的状态转移。
4. `IProtocolEffectExecutor` 或等价 executor bridge 是真实代码对象;平台只执行 `ProtocolEffect`,不决定
协议语义。
5. `MeshProtocolFacade` 或明确命名的等价 facade 是真实代码对象。它必须是 UI / ChatService 面向协议系统的
稳定用例入口,负责选择 runtime、构造 intent、执行 effects、回灌 tx result、返回 app-facing result。
6. Product factory / composition 不能散落在 UI 中。平台可以使用静态对象,但对象选择必须集中在产品组合层。
7. Incoming handler chain 的顺序和吞包语义必须写进 runtime/codec 边界,不能只靠平台 adapter 巨型 if
分支维持。
8. 如果规格中的核心类名只存在于 Mermaid 图中,而代码里没有真实对象或等价声明,则该项未完成。
## Current Implementation Gaps
截至本规格日期,runtime / effect 主干已经部分落地,但以下缺口仍然阻止本规格被判定为完成:
1. `MeshProtocolFacade` 还没有真实代码对象。当前若干 active UI / platform path 仍直接构造
`ProtocolIntent`、调用 `IProtocolRuntime`、遍历 `ProtocolEffects` 或手动处理 TX failure 回灌。
2. Product composition 还没有统一的 factory / bundle 边界。平台代码各自持有 runtime / executor,
组合逻辑仍可能继续散落。
3. Incoming handler chain 尚未以 `PacketHandling` 或等价返回语义显式化。部分平台 adapter 仍保留
巨型 decode/classify 分支,虽然其中若干协议决策已经下沉到 runtime/policy。
4. `RecordingProtocolExecutor` 已覆盖 effect 可记录性,但还缺少 `MeshProtocolFacade` 级别的 smoke,
用来证明 UI / ChatService 可以只通过 facade 发起 action 并观察 app-facing result。
## Migration Rules
1. Add shared contracts first: Intent, Effect, Runtime, Executor.
2. Add test-only `RecordingProtocolExecutor` to prove effects are inspectable.
3. Move one state machine at a time:
2. Add a real Facade boundary before claiming UI / ChatService decoupling:
- first create `MeshProtocolFacade` or update this spec with the exact equivalent code object;
- facade must hide runtime selection and effect execution from active UI / ChatService paths;
- add smoke tests that fail if the facade object disappears.
3. Add test-only `RecordingProtocolExecutor` to prove effects are inspectable.
4. Move one state machine at a time:
- Meshtastic PKI resync;
- Meshtastic TraceRoute/Position action lifecycle;
- Meshtastic NodeInfo/Position reply packet construction;
- MeshCore NodeInfo control payload build/parse;
- MeshCore trace action lifecycle.
4. Replace adapter logic with `executeEffect(...)` calls only after the shared state test exists.
5. Update `MeshCapabilities` when a protocol action becomes truly shared and executable.
6. Keep existing helper functions only as codec/building utilities, not as orchestration owners.
5. Replace adapter logic with `executeEffect(...)` calls only after the shared state test exists.
6. Update `MeshCapabilities` when a protocol action becomes truly shared and executable.
7. Keep existing helper functions only as codec/building utilities, not as orchestration owners.
## Review Checklist
@@ -501,9 +680,11 @@ Before changing protocol code:
2. Name the GoF pattern involved and why it is being used.
3. If changing a platform adapter, verify the change is only executor/adapter work.
4. If changing protocol semantics, put the rule in shared runtime and add a shared test.
5. Update this spec when the pattern boundary changes.
6. Update `PROTOCOL_ADAPTER_DRIFT_AUDIT.md` when a drift item is resolved or accepted.
7. Run GitNexus impact analysis before edits and `detect-changes` before commit.
5. If the spec names a core class or boundary, verify the code contains that object or an explicitly documented
equivalent. Mermaid-only architecture does not count as implementation.
6. Update this spec when the pattern boundary changes.
7. Update `PROTOCOL_ADAPTER_DRIFT_AUDIT.md` when a drift item is resolved or accepted.
8. Run GitNexus impact analysis before edits and `detect-changes` before commit.
## Relationship To Other Specs