# 公共站内通知发布与下游接入清单

## 下游生产者契约

业务事务必须先生成稳定 `event_id`，并与业务事实在同一事务写入公共 Outbox。载荷版本固定为 `1`，调用 `EnqueueTask` 时传 struct 或 map，禁止传预序列化 `[]byte`。

明确后台账号事件使用 `notification.admin.direct.requested`，载荷为 `AdminDirectPayload`；个人客户事件使用 `notification.personal_customer.direct.requested`；后台动态事件使用 `notification.admin.dynamic.requested`，目标只允许：

- `account + 稳定真实申请人账号 ID`
- `platform_role + 平台角色 ID`
- `shop + 目标店铺 ID`

动态店铺目标只产生当前启用主账号和当前可用业务员，不沿层级扩散。无接收人是正常终态。同一事件必须复用原 `event_id`，不得在重试时生成新 ID。

新增业务通知类型必须在代码注册表中明确：稳定类型、类别、级别、固定纯文本模板、允许模板字段、接收人类型和允许 `ref_type`。不得透传任意标题、正文、HTML、URL、Token、Secret、回调原文或长期附件地址。

## 目标与期限

通知只保存受控 `ref_type/ref_id/ref_key`。后台目标接口只返回前端白名单 `target_type` 和结构化 ID/Key，并再次复核当前权限；拥有通知不授予资源权限。

- 审批结果不自动过期，数据保留 365 天。
- 套餐临期必须携带业务到期时间，数据保留 180 天。
- 同步异常默认最多展示 30 天，数据保留 180 天。
- 系统告警默认展示 30 天、最长 365 天，数据保留 365 天。

## 测试环境发布顺序

1. 进入维护窗口并确认下游生产者尚未启用。
2. 执行迁移 `000168_create_notification` 和 `000169_add_shop_business_owner`，核对无外键表、唯一索引、查询索引及店铺业务员普通索引。
3. 发布 Worker，确认三个通知 Outbox 事件消费者、公共 `outbox:deliver` Handler 和每天 02:15 的 `notification:cleanup` 已注册。
4. 发布 API，核对后台、C 端路由和 OpenAPI；静态 `/read-all`、`/unread-count`、`/unread-summary` 必须可达。
5. 发布匹配的前端版本并按前端联调契约验收。
6. 最后启用 UR#33、UR#97、审批结果等下游生产者，避免消费者未就绪时制造不可见积压。

## 运行门禁与恢复

- 监控公共 Outbox pending/delivering/final failed、Asynq 重试与失败、通知 `no_recipient`、模板/解析失败和每日清理删除数。
- 出现永久模板错误、持续数据库错误、Outbox 积压或清理长期失败时，先停止对应下游生产者，不删除业务事实和已写通知。
- 已入队但未完成的事件继续使用原 `event_id` 恢复；不得要求用户重复提交或给同一业务生成新事件。
- 通知表已有事实后禁止执行 down 删除；应用回滚保留 `tb_notification` 和店铺业务员字段，修复后前向恢复。
- 不清理 Audit Event、Integration Log、Domain Ledger 或 Outbox，不使用通知列表替代业务审计。

## 当前验证状态

已完成路由、RouteSpec、集中式文档 Handler、Worker 消费者、模板、清理 Handler/调度和组合根的静态核对；已生成 `docs/admin-openapi.yaml`，并通过 `gofmt`、`git diff --check`、`go build ./...` 和 OpenSpec 校验。

按测试环境 Change 豁免，尚未执行真实 PostgreSQL 迁移、Redis/Relay/Asynq 端到端、真实认证 HTTP、并发重复消费或浏览器人工验收；这些门禁保持在任务 6.1、6.3、6.6，不能据此声明生产验收通过。
