diff --git a/.scratch/tech-inapp-notifications/issues/01-admin-direct-recipient-notification-loop.md b/.scratch/tech-inapp-notifications/issues/01-admin-direct-recipient-notification-loop.md new file mode 100644 index 0000000..cf5c781 --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/01-admin-direct-recipient-notification-loop.md @@ -0,0 +1,21 @@ +# 01 — 向明确后台账号可靠投递首条站内通知 + +**What to build:** 业务事件携带稳定后台账号 ID 后,可以经公共 Outbox、Relay 和 Notification Worker 为该账号幂等生成一条纯文本站内通知;当前登录账号可以查询自己的未读数和分页列表,并将单条通知幂等标记为已读。重复投递不会重复写入,过期通知不进入用户视图,任何用户接口都不能指定或篡改接收人。 + +**Blocked by:** `.scratch/tech-public-foundation/issues/03-outbox-at-least-once-delivery.md` — 03 — 完成 Outbox 到 Asynq 的至少一次投递闭环 + +**Status:** ready-for-agent + +**架构通道:** 主通道为简单写 Application,辅助通道为 Infrastructure 与 Query。 + +**完整业务边界:** 本票收口后台明确账号通知的存储、受控类型注册、Worker 幂等消费、未读数、基础列表和单条已读闭环。明确不实现角色或店铺动态接收人、个人客户通知、分类汇总、全部已读、目标解析、前端组件或具体业务场景触发规则,也不复制公共 Outbox 和 Relay。 + +- [ ] 通知事实包含稳定事件 ID、接收人类型与 ID、类别、类型、级别、纯文本标题正文、受控资源引用、已读与过期时间,并通过事件 ID、接收人类型和接收人 ID 唯一约束防止重复消费。 +- [ ] 通知常量、中文说明、类型到类别、默认级别、模板和允许目标的注册关系统一管理;未注册类型、模板字段永久缺失或正文包含禁止敏感内容时不生成残缺通知。 +- [ ] Worker 只接受结构化载荷,重复事件和并发消费最多为同一后台账号生成一条通知;瞬时数据库错误返回任务错误,原业务事务不因通知写入失败而回滚。 +- [ ] 当前后台账号可以获得准确的 `count:int64` 和 `display_count:string`,其中 0、1~99、100 以上分别显示 `0`、十进制文本和 `99+`,且未读数只查询 PostgreSQL。 +- [ ] 后台列表只读取当前认证账号的未过期通知,固定按创建时间和 ID 倒序,默认每页 20、最大 50,并返回统一响应与 ISO 8601 时间。 +- [ ] 单条已读使用接收人条件和未读条件更新;别人通知、不存在通知和已读通知均幂等成功,首次写入的 `read_at` 在重复请求中保持不变。 +- [ ] PostgreSQL、Worker 和真实后台认证 HTTP 集成测试覆盖唯一约束、重复消费、过期排除、分页排序、接收人篡改、越权隔离及重复已读。 +- [ ] 新增后台 Handler 后完成路由、RouteSpec 和两个 OpenAPI 文档生成器注册,且静态路由顺序不会被动态通知 ID 路由吞掉。 + diff --git a/.scratch/tech-inapp-notifications/issues/02-admin-notification-filter-summary-read-all.md b/.scratch/tech-inapp-notifications/issues/02-admin-notification-filter-summary-read-all.md new file mode 100644 index 0000000..86f2912 --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/02-admin-notification-filter-summary-read-all.md @@ -0,0 +1,20 @@ +# 02 — 交付后台通知筛选、分类汇总与全部已读 + +**What to build:** 当前后台账号可以按通知类别、类型、严重级别和已读状态分页查看自己的消息,获得总未读数及审批、临期、同步、系统四个固定类别的汇总,并将当前类别或全部未过期通知一次性标记为已读。筛选、汇总和更新始终绑定认证账号,不因管理员身份扩大到其他用户。 + +**Blocked by:** 01 — 向明确后台账号可靠投递首条站内通知 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Query,辅助通道为简单写 Application。 + +**完整业务边界:** 本票收口后台通知中心所需的筛选、固定分类汇总和批量已读用例。明确不实现个人客户接口、动态接收人、目标跳转、前端页面、Redis 未读计数或管理员查看他人通知能力。 + +- [ ] 列表支持类别、类型、严重级别、已读状态、页码和每页数量组合过滤,所有条件使用 AND 语义并保持创建时间、ID 倒序。 +- [ ] 非法类别、严重级别、已读参数或越界分页返回统一参数错误,不向客户端拼接底层校验信息。 +- [ ] 未读汇总固定返回 `total`、`approval`、`expiry`、`sync`、`system`,过期通知不计入任何分类。 +- [ ] 全部已读在类别为空时更新当前账号全部未过期未读通知,在类别有效时只更新该类别,并返回实际更新数量。 +- [ ] 批量更新使用当前接收人、未读状态、未过期和可选类别条件;重复调用返回零更新且保持成功,不覆盖既有 `read_at`。 +- [ ] `/read-all` 等静态路由先于 `/{id}` 动态路由注册,生成的 OpenAPI 与真实路由、请求参数和响应结构一致。 +- [ ] PostgreSQL 与真实后台认证 HTTP 集成测试覆盖组合筛选、固定汇总、最大分页、过期排除、非法类别、并发批量已读及无法操作他人通知。 + diff --git a/.scratch/tech-inapp-notifications/issues/03-personal-customer-notification-loop.md b/.scratch/tech-inapp-notifications/issues/03-personal-customer-notification-loop.md new file mode 100644 index 0000000..096487b --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/03-personal-customer-notification-loop.md @@ -0,0 +1,20 @@ +# 03 — 向个人客户投递并提供简化通知中心 + +**What to build:** 业务事件携带稳定个人客户 ID 后,可以为该客户幂等生成与其订单、套餐或资产有关的站内通知;当前登录个人客户可以查询自己的未读数和分页列表,并执行单条或全部已读。个人客户永远看不到同步、系统等平台运维消息,也不能通过请求参数读取或修改其他客户通知。 + +**Blocked by:** 01 — 向明确后台账号可靠投递首条站内通知 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Query,辅助通道为简单写 Application 与 Infrastructure。 + +**完整业务边界:** 本票收口个人客户通知的投递、读取和已读闭环,复用既有通知表、注册表和 Worker。明确不实现 C 端分类汇总、后台动态接收人、平台运维消息展示、C 端受控目标接口或前端组件。 + +- [ ] Worker 能以 `personal_customer` 接收人类型和稳定客户 ID 幂等生成通知,同一事件的后台账号与个人客户通知相互独立。 +- [ ] 个人客户通知类型注册明确允许的业务类别和资源引用;`sync`、`system` 及未对 C 端开放的类型不会出现在个人客户查询中。 +- [ ] C 端未读数遵循 0、1~99、100 以上的显示规则,列表固定倒序、默认每页 20、最大 50,并排除过期通知。 +- [ ] 单条已读、全部已读只作用于当前认证客户;不存在、已删除、属于别人或已读的通知使用相同幂等安全语义。 +- [ ] 请求 DTO 不接受接收人 ID,额外或恶意接收人参数不能改变查询与更新范围。 +- [ ] 真实个人客户认证、Handler、Query、GORM 集成测试覆盖重复投递、运维类别隔离、跨客户越权、分页、过期排除、重复已读和批量已读。 +- [ ] 新增 C 端 Handler 后同步个人客户路由、RouteSpec 与两个 OpenAPI 文档生成器,接口统一挂载在约定认证上下文中。 + diff --git a/.scratch/tech-inapp-notifications/issues/04-dynamic-recipient-resolution.md b/.scratch/tech-inapp-notifications/issues/04-dynamic-recipient-resolution.md new file mode 100644 index 0000000..c35b5b0 --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/04-dynamic-recipient-resolution.md @@ -0,0 +1,23 @@ +# 04 — 接入账号、角色与店铺动态接收人解析 + +**What to build:** Notification Worker 可以按业务场景把审批申请人、当前平台角色账号或目标店铺解析为一组稳定、去重且当前可用的后台账号接收人。店铺场景只包含当前启用的店铺主账号和当前仍可用的店铺业务员;账号停用、软删除或关系失效时跳过,上级代理不会因为可查看下级数据而自动收到通知。 + +**Blocked by:** + +- 01 — 向明确后台账号可靠投递首条站内通知 +- `.scratch/ur96-shop-business-owner/issues/06-shop-business-owner-notification-recipient-release.md` — 06 — 提供业务员通知接收人解析并完成发布验证 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Infrastructure Adapter,辅助通道为简单写 Application。 + +**完整业务边界:** 本票收口公共通知 Worker 对明确申请人、平台角色和店铺接收人的解析、可用性复核、去重及无接收人语义。明确不实现 UR#33、UR#97 或企微审批的业务触发规则,不改变账号、角色、店铺层级或业务员归属,不自动转派历史通知,也不发送短信或企微消息。 + +- [ ] 明确申请人场景只使用业务事件携带的稳定系统账号 ID,并在消费时跳过已停用或软删除账号,不使用企微代提交身份替代真实业务提交人。 +- [ ] 角色场景批量解析当前启用、未删除且仍持有指定平台角色的账号,结果按稳定账号 ID 去重,不按用户名或手机号投递。 +- [ ] 店铺场景解析当前启用的店铺主账号,并复用 UR#96 接缝解析当前可用业务员;不沿父店铺、祖先店铺或代理数据权限向上扩散。 +- [ ] 同一账号同时以主账号、业务员或角色命中时只生成一条通知,同一事件的其他接收人仍分别拥有独立已读状态。 +- [ ] 暂无可用接收人记为 `no_recipient` 并成功结束,不进入无限重试;数据库等瞬时错误继续返回任务错误。 +- [ ] 已生成通知不会因账号关系后续变化而转移给新接收人,历史接收人仍可在自身认证上下文中读取原通知。 +- [ ] Application、Worker 和 PostgreSQL 集成测试覆盖申请人、角色批量解析、店铺主账号与业务员去重、停用、软删除、关系失效、无接收人及重复投递。 + diff --git a/.scratch/tech-inapp-notifications/issues/05-controlled-notification-target-resolution.md b/.scratch/tech-inapp-notifications/issues/05-controlled-notification-target-resolution.md new file mode 100644 index 0000000..8d4f372 --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/05-controlled-notification-target-resolution.md @@ -0,0 +1,23 @@ +# 05 — 交付通知受控目标解析与权限复核 + +**What to build:** 当前后台账号点击自己的通知时,后端只返回白名单目标类型和结构化目标标识,不保存也不返回任意 URL。退款、代理充值、企微审批、卡、设备、临期资产、店铺资金、审计外部集成和系统配置等目标在解析时重新执行当前资源权限检查;目标不存在或权限已经变化时统一返回 `available=false`,不泄露资源详情。 + +**Blocked by:** + +- 01 — 向明确后台账号可靠投递首条站内通知 +- `.scratch/tech-global-audit/issues/13-request-correlation-integration-timeline.md` — 13 — 交付请求、业务链路和外部集成时间线 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Query,辅助通道为 Application + Port/Adapter。 + +**完整业务边界:** 本票收口后台通知受控目标注册、解析、当前权限复核和安全不可用语义。明确不返回 URL、不实现前端路由构造、不把通知所有权当成目标资源权限、不创建独立卡同步执行页,也不迁移各目标业务详情的既有授权规则。 + +- [ ] 通知只保存受控资源类型、数值 ID 或稳定 Key;目标响应只包含白名单 `target_type`、结构化 `target_id/target_key` 和 `available`,任何字段均不能承载任意 URL。 +- [ ] 第一版注册表至少覆盖退款详情、代理充值详情、企微审批详情、卡详情、设备详情、临期资产列表、店铺资金概况、审计外部集成和系统配置。 +- [ ] 目标解析先按当前接收人固定查询通知,再调用对应业务权限 Adapter 复核资源;拥有通知不授予目标资源访问权。 +- [ ] 别人通知、不存在通知和已删除通知不泄露通知事实;目标不存在、已删除或当前无权时统一返回 `available=false`,不返回资源差异信息。 +- [ ] `card_sync` 等同步消息解析为统一审计中心外部集成目标并携带受控资源或 Integration Log 标识,不指向不存在的同步执行页。 +- [ ] 未知通知引用或尚未支持的目标只允许展示正文,不产生开放重定向、自由路径或自动回退 URL。 +- [ ] 契约与越权测试覆盖全部白名单、未知引用、通知越权、目标删除、权限变化、外部集成目标,并断言响应和持久化数据不存在任意 URL。 + diff --git a/.scratch/tech-inapp-notifications/issues/06-notification-retention-observability.md b/.scratch/tech-inapp-notifications/issues/06-notification-retention-observability.md new file mode 100644 index 0000000..de203b6 --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/06-notification-retention-observability.md @@ -0,0 +1,24 @@ +# 06 — 交付通知保留清理与失败可观测闭环 + +**What to build:** 系统可以按通知场景计算展示期限和数据保留期限,并在低峰按稳定主键和时间分批删除已超过保留期的通知。无接收人、模板错误、系统告警生成和投递失败具有可追踪、有限重试和安全摘要,既不会无限重试,也不会影响已经提交的资金、审批、套餐或其他业务事实。 + +**Blocked by:** + +- 01 — 向明确后台账号可靠投递首条站内通知 +- `.scratch/tech-global-audit/issues/01-audit-event-write-loop.md` — 01 — 交付不可变 Audit Event 写入闭环 +- `.scratch/tech-global-audit/issues/02-integration-log-attempt-loop.md` — 02 — 交付可恢复的 Integration Log 尝试闭环 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Infrastructure,辅助通道为简单写 Application。 + +**完整业务边界:** 本票收口通知展示期限、数据保留、分批清理、失败分类和统一审计/外部集成可观测接缝。明确不删除 Audit Event、Integration Log、领域流水或 Outbox,不提供用户删除接口,不建设管理员查看他人消息入口,也不改变公共 Relay 的租约算法。 + +- [ ] 套餐临期、审批结果、同步异常和系统告警按约定计算展示与保留期限,审批结果不自动过期,系统告警展示期限不超过允许上限。 +- [ ] 清理任务按时间和稳定主键小批量删除超过数据保留期限的通知,可中断重跑且只清理通知事实,不级联业务资源或审计记录。 +- [ ] 暂无接收人记录 `no_recipient` 后成功结束;数据库、队列等瞬时错误按有限策略重试;模板永久缺失达到最大重试后进入失败监控且不写残缺正文。 +- [ ] 系统告警直接入队时必须携带预先生成的稳定事件 ID,重复执行仍由通知唯一键防重。 +- [ ] 模板或接收人解析失败、系统告警生成和管理性排查写入统一 Audit/Integration 接缝;普通通知读取和已读只进入 Access Log。 +- [ ] 日志、监控和审计只记录事件 ID、通知类型、失败类别、计数及安全资源标识,不记录敏感模板数据、完整回调、Token、Secret 或任意长期 URL。 +- [ ] 测试覆盖各类期限边界、分批清理可重入、无接收人、瞬时失败、永久模板失败、最大重试、系统告警重复入队和业务事实不回滚。 + diff --git a/.scratch/tech-inapp-notifications/issues/07-frontend-contract-acceptance-pack.md b/.scratch/tech-inapp-notifications/issues/07-frontend-contract-acceptance-pack.md new file mode 100644 index 0000000..35b91be --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/07-frontend-contract-acceptance-pack.md @@ -0,0 +1,24 @@ +# 07 — 冻结后台与 C 端通知前端契约及验收包 + +**What to build:** 前端仓库获得稳定、框架无关的后台铃铛、最近通知抽屉、通知中心和个人客户简化列表契约,以及可执行的验收数据。前端可以实现 30 秒未读轮询、页面隐藏暂停、失败保留旧值、筛选和全部已读,并按“先已读、再解析受控目标”的顺序处理点击,而无需猜测类别、级别或后端目标路径。 + +**Blocked by:** + +- 02 — 交付后台通知筛选、分类汇总与全部已读 +- 03 — 向个人客户投递并提供简化通知中心 +- 05 — 交付通知受控目标解析与权限复核 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Query/API 跨仓契约,辅助通道为前端验收契约。 + +**完整业务边界:** 本票收口当前后端仓库能够交付的 OpenAPI、交互状态、目标白名单说明和验收数据,不在本仓库实现前端组件。明确不引入 WebSocket/SSE,不承诺 Redis 未读计数,不为未知目标提供自由 URL,也不代替前端仓库自身的组件测试。 + +- [ ] 契约明确布局挂载后立即请求、每 30 秒刷新、页面不可见暂停、恢复立即刷新,以及失败保留上次成功未读数且不闪回零。 +- [ ] 徽标验收覆盖 0、1、99、100,0 时隐藏、1~99 显示数字、100 显示 `99+`,并约定固定宽度避免布局抖动。 +- [ ] 后台抽屉按最近 10 条和约定分类展示,通知中心支持类别、类型、严重级别、已读状态、服务端分页及当前类别全部已读。 +- [ ] 点击顺序固定为先进入已读视觉状态并调用已读接口,再解析受控目标;已读失败以下次服务端刷新为准,目标失败不恢复未读。 +- [ ] 前端目标白名单只根据 `target_type` 和结构化标识构造内部路由,未知类型或 `available=false` 只展示正文且不跳转。 +- [ ] C 端契约只展示当前客户相关的审批结果、套餐、订单和资产消息,不暴露后台同步或系统运维分类。 +- [ ] OpenAPI、中文契约文档、示例响应与验收矩阵保持一致,并明确前端代码位于外部仓库、需按对应仓库流程实施和联调。 + diff --git a/.scratch/tech-inapp-notifications/issues/08-notification-release-gate.md b/.scratch/tech-inapp-notifications/issues/08-notification-release-gate.md new file mode 100644 index 0000000..0a38738 --- /dev/null +++ b/.scratch/tech-inapp-notifications/issues/08-notification-release-gate.md @@ -0,0 +1,27 @@ +# 08 — 完成公共通知发布门禁与下游接入契约 + +**What to build:** 发布负责人可以通过一套公共站内通知整体验收确认通知表、注册表、Worker、后台和 C 端接口、受控目标、清理任务及运行监控已经就绪。验收使用真实 PostgreSQL、Redis、公共 Outbox Relay 和 Asynq 接缝验证至少一次投递与重复消费,并向 UR#33、UR#97 和企微结果通知提供稳定的事件、接收人、模板和目标注册方式。 + +**Blocked by:** + +- 02 — 交付后台通知筛选、分类汇总与全部已读 +- 03 — 向个人客户投递并提供简化通知中心 +- 04 — 接入账号、角色与店铺动态接收人解析 +- 05 — 交付通知受控目标解析与权限复核 +- 06 — 交付通知保留清理与失败可观测闭环 +- 07 — 冻结后台与 C 端通知前端契约及验收包 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Infrastructure,辅助通道为 Application、Query 与跨仓契约。 + +**完整业务边界:** 本票收口公共通知的迁移验证、端到端可靠性、OpenAPI、文档、发布回滚和下游接入说明。明确不实现 UR#33 套餐临期、UR#97 钱包低余额或企微审批结果的业务触发规则,不发送真实用户测试通知,不引入外部 Delivery 渠道,也不借发布验收迁移未触碰旧模块。 + +- [ ] 空数据库和兼容环境可执行通知正向迁移、索引校验与允许的结构回滚;已有通知事实后不得通过降级删表清理,应用回滚允许保留数据。 +- [ ] PostgreSQL、Redis、公共 Relay 和 Asynq 端到端测试覆盖事务事件、至少一次投递、入队成功后重复、并发 Worker、多接收人、接收人去重和最终通知唯一性。 +- [ ] 真实后台与个人客户认证测试覆盖所有公开接口、统一响应、静态路由顺序、接收人不可篡改、跨用户隔离、过期排除和受控目标权限变化。 +- [ ] 运行门禁覆盖 Worker 失败、永久模板错误、无接收人、Outbox 积压、清理滞后和审计/外部集成记录异常,并给出停止放量和恢复步骤。 +- [ ] 下游接入契约明确稳定事件 ID、结构化载荷、受控通知类型、接收人解析、模板字段、过期策略和目标引用;调用统一队列客户端时禁止传预序列化字节。 +- [ ] 发布顺序明确为迁移与校验、Worker 与监控、后端 API、前端、下游生产者;下游不得在消费者和监控就绪前制造不可见积压。 +- [ ] 新增管理端和 C 端 Handler 已同步路由、RouteSpec、两个 OpenAPI 文档生成器,并生成 OpenAPI 核对 `/read-all` 未被动态 ID 路由吞掉。 +- [ ] 中文功能总结覆盖关键流程、前后端契约、异常闭环、监控、发布回滚和待决策项,README 增加入口;测试数据使用隔离标识且不向真实用户生成通知。