Skip to content

概念边界与分层审计 ​

状态:现行裁决(2026-10-07,依据外部架构评审逐条核处)。本文回答一个问题:受众层的每个词管什么、谁不能入侵谁。术语的含义定义在总纲 §2(唯一口径);本文是边界裁决——重叠排查的结论、分层地图、以及后续批次落地前必须对照的红线。两文冲突时以总纲 §2 为准,本文跟着改。

1. 定位口径 ​

Herald 是轻量的通知编排与投递基础设施(lightweight notification orchestration and delivery infrastructure)。

「编排」二字是本次评审钉进定位的:Herald 从来不只是 send()——它决定谁、什么时候、通过什么渠道、以什么强度、是否聚合、是否过滤、是否升级、是否去重。投递管道(Phase 0-9 已落地)是地基;受众层(统一订阅与投递中枢)把「被通知者做主」立起来;编排是这两者之上的行为描述。README、总纲 §1、架构概览三处定位句已同步。

2. 五层地图 ​

系统的分层边界,后续批次的功能必须先回答「落在哪一层」再动手:

text
┌────────────────────────────────────────────────────────────────┐
│ 集成层    API(/notify /audience /feeds)· Go SDK · webhook 回调 │
│          来源适配器(bot / 公众号 / 应用内勾选)                   │
├────────────────────────────────────────────────────────────────┤
│ 受众与偏好层  Registry · SurfaceRegistry · PreferenceRegistry    │
│          Relation(订阅/指派)· Filter/矩阵 · 审计流水            │
├────────────────────────────────────────────────────────────────┤
│ 编排层    规则引擎 · 路由展开 · 去重/频控 · Digest 聚合            │
│          紧急度×强度匹配 · 投递模式执行(升级链/单渠道/并行)       │
├────────────────────────────────────────────────────────────────┤
│ 投递运行时层  Planner→Task · Queue · Worker · Retry · ack · 状态机│
├────────────────────────────────────────────────────────────────┤
│ Provider 层  telegram / email / webhook / wechatmp / …(可换实现)│
└────────────────────────────────────────────────────────────────┘

代码落位(双栖件注明主层):

层包/模块
集成层api/(HTTP 面)、worker-sdk/go/、来源适配器与 webhook 回调(批次 8/11)
受众与偏好层core/audience/(注册表/联系面/偏好/过滤)、core/audit/
编排层core/rules/、core/route/(路由表)、core/dedup/、core/digest/、core/escalation/;强度×紧急度匹配与三模式执行器(批次 9)
投递运行时层core/service/(planner/pool——编排决策在此落成任务)、core/queue/、worker、core/retry/、core/ack/
Provider 层providers/builtin/、core/errclass/(错误词汇属 provider 契约)

3. 八词裁决:无重叠 ​

评审点名排查的概念——受众 / 渠道 / 联系面 / provider / 偏好 / 投递模式 / 紧急度 / 侵扰度——逐词核处如下,结论:各自回答不同的问题,不合并、不换名。

词归属侧回答的问题边界红线
受众 audience身份「通知谁」全系统唯一 ID;不存渠道凭据;不再派生同位身份实体(§5)
渠道 channel触达手段「走哪类通道」带侵扰度、实时性、投递方向(§4)三个属性;渠道≠provider
联系面 ContactSurface凭据实例「这个受众在这条渠道上的具体地址」渠道的运行时绑定态;静态配置里的 recipients/endpoints 只是它的种子
provider投递实现「用哪个实现投出去」渠道经渠道块展开为 provider 实例;同一渠道可换实现(email→smtp 或 resend);强度阶梯按渠道类标定,provider 继承所在级
偏好 Preference用户选择「受众自己要什么」只能收窄不能放宽(静默底线、频控同理);存在订阅关系上
紧急度 Urgency消息侧「这事有多紧急」决定允许的强度区间(详设 §6.2);品类默认映射,单事件可覆盖
侵扰度 Intensity渠道侧「这条渠道有多打扰」L0–L5 阶梯;与紧急度被 §6.2 矩阵连接——一个在消息上、一个在渠道上,永远不碰头
投递模式编排侧「怎么投:升级链/固定单渠道/并行」策略件同一入口不同执行器(批次 9);与规则引擎升级正交(规则升级=叫谁,模式升级=怎么叫得更响)

评审担心的「Intensity=high / Urgency=high 分不清」在本仓不成立,因为两者不共侧:紧急度是消息的属性,侵扰度是渠道的属性,二者只在匹配矩阵(详设 §6.2)相遇。评审建议的「Intensity=投递策略多强」这一轴已存在,名字叫投递模式(escalation/fixed/parallel)——不再造第三个「强度」词。

4. 投递方向:Push / Pull ​

评审指出 RSS 改变了投递模型(推 vs 拉),正确。裁决:

  • 新增投递方向(push / pull)作为渠道属性:推送渠道由 Herald 主动投(telegram/email/短信/电话/站内信),拉式渠道由阅读者轮询(RSS)。
  • RSS 是拉式渠道,不是一种 provider:RSS feed 是投递记录的拉式投影(详设 §9),走同一套「关系允许×联系面绑定」校验。
  • 命名避让:不采用「Push/Pull Delivery Mode」的叫法——「投递模式」一词已被升级链/固定单渠道/并行三选占用(总纲 §2 术语表),一个词不能有两个含义。方向轴落名「投递方向」,写进渠道属性。

批次 7(RSS)已按此口径落地:core/feeds 存储拉式投影,/feeds/** 属集成层的拉式出口,rss 类渠道在投递管道就地投影、不产生 provider 任务,可见性校验复用受众层(token 归属经 SurfaceRegistry、品类准入经关系注册表+矩阵复核),不新增投递状态机分支;多地址容灾归阅读器侧,不代码化。

5. 幂等 / 去重 / 频控 / 聚合:四件事四道闸 ​

评审的四分法与本仓现状对齐(详设 §10/§11),措辞收紧如下,四个概念永不混叫:

概念解决什么闸门位置现状
幂等 idempotency_key/event_id同一事件的重复提交(重试风暴、上游重发)管道最前已落地(Phase 0)
折叠/状态机去重等价内容的重复投递(×N 计数、状态翻转才发)编排层,投递之前、升级链之前折叠已落地;状态机批次 10
频控 once/throttle/always时间窗内的频率上限去重之后批次 10
聚合 Digest多条不同消息合一条摘要(受众×品类×时间窗)独立旁路,摘要走全投递链已落地(批次 6)

先后关系:幂等 → 折叠/状态 → 频控 → (旁路)聚合 → 投递。审计对每道闸都有独立事件词汇(delivery.deduped 等),回答「为什么这条没投」。

6. 评审七点逐条回执 ​

#评审意见处置
①定位升级为「通知编排与投递」已改:总纲 §1、README、架构概览三处定位句加「编排」(§1)
②Surface 立为核心概念已是现状:联系面链 受众→接收人→联系面→渠道→provider(详设 §2 关键约束);本次补显式句「渠道≠provider,可换实现」进总纲 §2
③Push/Pull 立为投递模型轴采纳并改名:落为渠道属性「投递方向」,避让「投递模式」一词(§4)
④Urgency/Intensity 重新定义或合并核实为已分层:消息侧 vs 渠道侧,§6.2 矩阵连接;「策略强度」轴已由投递模式承担(§3)
⑤幂等/去重/频控/聚合四分已四分,措辞收紧(§5)
⑥Redis 不做 Digest 硬依赖批次 6 落地即合规:digest.LeaderLock 构造注入,配置无 redis_addr 时为 nil、单机进程内定时器直跑;多实例才配租约。锁实现可换(接口只依赖 SET NX/续约/释放语义)
⑦停止新增受众子概念立为红线(§7)

7. 红线:受众层实体封顶 ​

受众层实体清单封顶为七个:受众、接收人、端点、联系面、关系(订阅/指派)、偏好、聚合规则。评审点名的 Subscriber / Member / Contact / Identity / Profile / Target / Destination 等同位实体不再新增——受众是身份,订阅者/接收者是关系角色,这个分层(详设 §1)已经覆盖上述全部词义。新需求一律落为既有实体的属性或关系;确需第八个实体时,先改总纲 §2 术语契约并过一遍本文 §3 的八词排查,再动代码。

8. 批次回执与后续纪律 ​

评审对批次 1-11 的分级判断与落地状态对照(落地详情见总纲 §13):

批次评审判断本仓处置
1 关系模型非常合理✅ 已落地
2 联系面与绑定合理,核心能力✅ 已落地
3 偏好中心非常合理✅ 已落地
4 关系过滤非常重要✅ 已落地
5 审计应保留✅ 已落地
6 Digest合理但后置✅ 已落地(redis 可选合规);评审的「后置」指向当时未落地,不追溯
7 RSS应定义为拉式渠道✅ 已按 §4 口径落地(core/feeds + /feeds/**,拉式投影不产生 provider 任务)
8 来源适配器合理✅ 已按「订阅入口适配器」口径落地(SourceAdapter 动作归一化 + bot/公众号/应用内三端点 + Reconciler 定期对账;详设 §8 落地段)
9 强度与模式有价值,需钉边界◑ 按 §3 八词裁决落地——Urgency/Intensity/投递模式三轴不合并
10 去重频控必须做◑ 按 §5 四分口径落地
11 集成者 API/SDK非常合理◑ token 权限按评审意见最小化起步:notify/query 先行,config 类权限收紧到管理面评审后再开

后续纪律:批次 7-11 每批动手前,先对照 §2 五层地图回答「落在哪层」、对照 §3/§5 回答「用哪个既有词」;与本文冲突的实现不进门禁。