helix_core/effect/operation.rs
1use bytes::Bytes;
2
3use super::{
4 Correlation, DomainEventBytes, FileUploadRequest, HttpRequest, StorageOp, TimerId, TransportId,
5};
6
7/// 模块吐出的 I/O 意图声明(tagged union,非法组合不可表达)。
8///
9/// **执行顺序契约(EFFECT-5)**:同一个 step 产出的 `Vec<Effect>` 之间**无兑现顺序保证**——
10/// driver 可乱序/并发兑现(如 Persist 入队即返回,Send/Emit inline await)。需要顺序的因果
11/// **必须经 `Correlation` 串联**(吐 Request/Persist{corr} → 等 `PortReply` → 再吐下一步),
12/// 不可依赖 `Vec` 下标顺序。
13#[derive(Debug)]
14pub enum Effect {
15 /// 发送 WS 帧(driver 调用 FrameSender::send)
16 Send {
17 transport: TransportId,
18 frame: Bytes,
19 },
20
21 /// 需成功回报的持久化:带 Correlation。
22 /// driver 执行后**必须**产出 `Tick::PortReply{corr, outcome}`。
23 /// 典型用途:batch_upsert 消息(写成功才推 cursor)。
24 Persist {
25 corr: Correlation,
26 ops: Vec<StorageOp>,
27 },
28
29 /// 需成功回报的**跨 StorageOp 原子**持久化:带 Correlation。
30 ///
31 /// 与 [`Self::Persist`] 有意分开:`Persist` 保留既有逐操作 fail-fast 语义,不能被悄悄
32 /// 升格为事务;只有调用方明确选择本变体时,driver 才必须把全部写操作放进同一原子提交单元。
33 /// 驱动不具备该能力时必须回 `PortReply::Err`,绝不能降级成普通 `Persist`。
34 ///
35 /// `ops` 仅允许写操作;需要读取结果的业务必须用相关性分成后续 step,避免把读侧语义混入
36 /// 跨平台事务承诺。
37 PersistAtomic {
38 corr: Correlation,
39 ops: Vec<StorageOp>,
40 },
41
42 /// 幂等 fire-and-forget 持久化:不带 Correlation,不产出 PortReply。
43 ///
44 /// ## 约束(强制)
45 ///
46 /// 仅用于满足以下**全部**条件的写操作:
47 /// 1. 幂等:重复执行结果相同(如 monotonic_upsert 的 MAX guard 天然幂等)
48 /// 2. 丢失可接受:写入失败后,下次操作可从旧状态重新推导正确结果
49 ///
50 /// **禁止**用于:
51 /// - 首次落库的业务数据(消息、用户数据等)
52 /// - 任何依赖"写入成功"才能继续的操作
53 ///
54 /// 典型用途:在 `Persist{corr}` 的 PortReply Ok 后推进单调水位(如同步 cursor)。
55 PersistFire { ops: Vec<StorageOp> },
56
57 /// HTTP 请求(**必达**语义):带 Correlation。
58 /// driver 执行后**必须**产出 `Tick::PortReply{corr, outcome=Ok(resp)/Err}`。
59 ///
60 /// ## 背压契约(与 `Persist` 对称,刀1 翻默认)
61 ///
62 /// driver 对必达 `Http` 在超载时**只能 block-back**(队满 `.await` 把背压传导回
63 /// tick_tx,慢而不丢),**绝不 drop**——因为 PortReply 是 load-bearing:响应数据 /
64 /// inflight-gate 清除 / cursor 推进都依赖它。
65 /// 「满即丢本条」降级为显式 opt-in(见 `HttpFire`),不再是 Http 的全局默认。
66 Http { corr: Correlation, req: HttpRequest },
67
68 /// 本地文件上传(**必达**语义):带 Correlation。
69 /// driver 读取 `local_path` 指向的本地文件并执行真实上传后,**必须**产出
70 /// `Tick::PortReply{corr, outcome=Ok(resp)/Err}`。
71 UploadFile {
72 corr: Correlation,
73 req: FileUploadRequest,
74 },
75
76 /// 幂等 fire-and-forget HTTP(**可丢弃·丢失可自愈**):不带 Correlation,不产出 PortReply。
77 ///
78 /// 与 `PersistFire` 对称——driver 对 `HttpFire` 用 DropNewest 溢出策略
79 /// (队满丢本条/最新来的 + warn),泵不阻塞。
80 ///
81 /// ## 约束(强制,与 `PersistFire` 同口径)
82 ///
83 /// 仅用于满足以下**全部**条件的请求:
84 /// 1. 不需要响应体(HTTP 仅触发 / ack,真数据走其它通道如 WS)
85 /// 2. 丢失可自愈:丢了由后续 cursor-gate / 重连 / proactive resync 重发补偿
86 /// 3. 无 inflight 闸依赖其 PortReply 清除
87 ///
88 /// **禁止**用于:
89 /// - posts/create(发消息必达)
90 /// - 任何 PortReply 推进状态机的请求(sync/notify、increment 等——它们的 PortReply
91 /// 承载数据 / 清闸 / 推 cursor,丢了会自锁;这类必须用 `Http`)
92 ///
93 /// 当前 helix-im 无真 fire-and-forget HTTP(全部必达),HttpFire 暂为空桶,
94 /// 为未来正确的请求类(如 telemetry ping / presence 心跳 HTTP)预留正确归属,
95 /// 并让「Http 默认必达 Block」在类型层成立(opt-in 才放松)。
96 HttpFire { req: HttpRequest },
97
98 /// 主动请求宿主(EventBus REP 模式·方向① core→host→core):带 Correlation。
99 ///
100 /// 与 `Http`/`Persist` 同构——driver 把请求转交宿主(UI / FFI host / JS),
101 /// 宿主处理后**必须**产出 `Tick::PortReply{corr, outcome}`,
102 /// 经 `ExecutionShell::corr_map` 定向回投给发起该 corr 的模块。
103 ///
104 /// - `kind`:编译期常量路由键(如 "ask_user" / "host_config"),core 不解析其含义
105 /// - `payload`:已序列化的请求字节(由模块 ACL 序列化)
106 ///
107 /// ## 与 `Emit` 的区别
108 ///
109 /// `Emit` 是单向 PUB(fire-and-forget,无回音);
110 /// `Request` 是双向 REP 的**发起端**(要回音,靠 corr 配对,复用 PortReply 回路)。
111 ///
112 /// **driver 实现契约(EFFECT-2)**:driver 必须兑现并回灌 `Tick::PortReply{corr}`;若是
113 /// deferred stub(如 native engine_loop 当前只 warn 不回灌),该 corr 会永久滞留 corr_map、
114 /// inflight 闸不清——接线缺口在「第二个发起 Request 的模块接入」时暴露(与 Http/Persist 同口径)。
115 Request {
116 corr: Correlation,
117 kind: &'static str,
118 payload: Bytes,
119 },
120
121 /// 发布领域事件(同步 push,不等待)。
122 /// driver 调用 EventSink::emit,事件字节已在 ACL-1 序列化。
123 Emit { event: DomainEventBytes },
124
125 /// 调度定时器:`after_ms` 毫秒后产出 `Tick::Timer{id}`。
126 /// driver 维护 TimerRegistry;id 由模块分配(建议模块维护 IdSource)。
127 ScheduleTimer { id: TimerId, after_ms: u64 },
128
129 /// 取消定时器(幂等,id 不存在时静默忽略)。
130 CancelTimer { id: TimerId },
131}