From 7c8a4cd3285d7ec19cf8210cce7ad9b8d9f93fc5 Mon Sep 17 00:00:00 2001 From: break Date: Wed, 22 Jul 2026 17:49:25 +0900 Subject: [PATCH] ur40issues --- ...-purchasability-and-history-eligibility.md | 21 +++++++++++++ ...2-normal-package-marketplace-projection.md | 18 +++++++++++ .../03-current-package-renewal-projection.md | 18 +++++++++++ .../04-package-history-renewal-projection.md | 19 ++++++++++++ ...lient-order-renew-only-write-validation.md | 20 +++++++++++++ ...n-personal-entrypoint-shelf-enforcement.md | 19 ++++++++++++ .../issues/07-renew-only-audit-events.md | 22 ++++++++++++++ ...tract-release-and-entrypoint-acceptance.md | 30 +++++++++++++++++++ 8 files changed, 167 insertions(+) create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/01-unified-package-purchasability-and-history-eligibility.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/02-normal-package-marketplace-projection.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/03-current-package-renewal-projection.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/04-package-history-renewal-projection.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/05-client-order-renew-only-write-validation.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/06-non-personal-entrypoint-shelf-enforcement.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/07-renew-only-audit-events.md create mode 100644 .scratch/ur40-off-shelf-package-renewal/issues/08-contract-release-and-entrypoint-acceptance.md diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/01-unified-package-purchasability-and-history-eligibility.md b/.scratch/ur40-off-shelf-package-renewal/issues/01-unified-package-purchasability-and-history-eligibility.md new file mode 100644 index 0000000..4aa73e4 --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/01-unified-package-purchasability-and-history-eligibility.md @@ -0,0 +1,21 @@ +# 01 — 建立统一套餐可售决策与真实历史资格 + +**What to build:** 系统可以基于调用主体、资产当前归属与世代、套餐、销售渠道和操作场景,统一给出 `normal`、`renew_only` 或 `disabled` 的购买决策、稳定中文原因及当前有效价格。下架续费资格只来自当前客户在同一资产、当前世代、同一套餐上的真实有效购买历史,不会被退款、撤销、转手或异常使用记录误放行。 + +**Blocked by:** None — can start immediately + +**Status:** ready-for-agent + +**架构通道:** 主通道为复杂写前置 Domain Policy,辅助通道为 Infrastructure Port/Adapter;查询侧只能消费同一策略能力进行投影。 + +**完整业务边界:** 本票收口 UR#40 的套餐可售决策、真实历史资格与当前渠道定价语义,为 C 端 Query、C 端创建订单及其他购买入口提供唯一接缝。明确不迁移未触碰的订单创建、支付、退款、资产生命周期或套餐管理用例,不新增资格表、缓存、快照或续费订单类型。 + +- [ ] 策略输入显式包含 actor、asset、当前 generation、package、sales channel 和 operation context,不使用 `is_renewal` 等隐式布尔开关,也不信任调用方提供的客户、资产类型、店铺或上下架结论。 +- [ ] 套餐全局禁用时固定返回 `disabled`;平台渠道读取套餐上下架状态,代理渠道要求当前资产店铺对应的分配记录存在、未软删除、已启用,并读取其独立上下架状态。 +- [ ] 正常在售且满足既有系列、赠送套餐、主套餐/加油包组合及价格规则时返回 `normal`;渠道下架时仅个人客户本人在 C 端续费场景可以进入历史资格判断,其他主体和场景固定拒绝。 +- [ ] 有效历史通过 `EXISTS` 或等价批量查询判断,只接受当前资产、当前世代、同一套餐、未软删除且状态为待生效、生效中、已用完或已过期的使用记录;已失效记录不产生资格。 +- [ ] 历史资格同时联合订单与退款事实:待支付、取消、退款、已有 `refund_id`、撤销后失效、赠送、后台发放或其他无真实个人购买事实的记录均不得产生资格,不能只依赖单个可能滞后的状态字段。 +- [ ] 平台渠道使用下单时套餐当前有效零售价;代理渠道使用当前有效分配零售价并校验不低于当前授权成本价;历史订单价格和使用记录实付金额不参与定价。 +- [ ] 决策返回稳定原因,至少覆盖套餐已禁用、套餐已下架仅历史用户可续费、当前资产无该套餐有效历史、当前渠道未授权该套餐、套餐价格配置异常及既有主套餐/加油包限制,调用方不需要解析文案推导状态。 +- [ ] 单元与 PostgreSQL 集成测试覆盖平台/代理、启用/禁用、上架/下架、分配缺失/禁用/软删除、价格异常、赠送,以及历史状态、退款撤销、资产、套餐、客户和世代隔离矩阵。 +- [ ] 使用真实查询计划核验历史资格查询;只有确认现有生产结构缺少适用索引时,才通过可升降级迁移补充覆盖资产、世代、套餐、状态和软删除条件的普通或部分索引,且不建立外键。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/02-normal-package-marketplace-projection.md b/.scratch/ur40-off-shelf-package-renewal/issues/02-normal-package-marketplace-projection.md new file mode 100644 index 0000000..a5fabe5 --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/02-normal-package-marketplace-projection.md @@ -0,0 +1,18 @@ +# 02 — 套餐商城只展示正常在售套餐 + +**What to build:** 当前资产所有人查看套餐商城时,只能看到当前销售渠道正常启用、上架且满足既有购买规则的套餐。每个返回项携带统一购买资格字段,下架套餐即使拥有历史续费资格也不会重新进入商城或成为新购入口。 + +**Blocked by:** 01 — 建立统一套餐可售决策与真实历史资格 + +**Status:** ready-for-agent + +**架构通道:** Query。 + +**完整业务边界:** 本票只迁移和收口 `GET /api/c/v1/asset/packages` 的读取、权限、批量可售投影与 DTO 契约。明确不提供下架续费入口、不修改订单、不迁移资产信息或套餐历史查询、不经过聚合根。 + +- [ ] 查询继续执行个人客户认证和当前资产有效绑定校验,无权与资源不存在保持统一安全错误语义。 +- [ ] 商城只返回统一策略判定为 `normal` 的套餐,并保持既有系列、赠送套餐、主套餐/加油包、价格、排序和空结果语义。 +- [ ] 下架套餐无论是否存在当前资产历史资格都不会出现在商城;套餐全局禁用、代理分配缺失、禁用、软删除或价格异常时也不会返回。 +- [ ] 每个正常返回项稳定包含 `can_purchase=true`、`purchase_mode=normal` 和 `disabled_reason=""`,现有套餐 ID、名称、类型、价格及流量字段保持兼容。 +- [ ] Query 使用批量读取或等价集合策略解析套餐与代理分配,不在循环中逐项访问数据库,也不把查询结果用于后续写入授权。 +- [ ] HTTP 集成测试穿过真实 Fiber 认证、Handler、Query、GORM/PostgreSQL 和统一响应,覆盖平台与代理渠道、下架历史用户、禁用、未授权、价格异常及正常上架新购不回归。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/03-current-package-renewal-projection.md b/.scratch/ur40-off-shelf-package-renewal/issues/03-current-package-renewal-projection.md new file mode 100644 index 0000000..2a5d83a --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/03-current-package-renewal-projection.md @@ -0,0 +1,18 @@ +# 03 — 当前套餐提供下架续费入口 + +**What to build:** 当前资产所有人查看资产信息时,可以获得当前套餐的稳定套餐 ID 和服务端购买资格。当前渠道已下架但存在真实有效历史时,当前套餐明确显示为仅限该资产续费;资格不成立时返回不可购买及稳定中文原因。 + +**Blocked by:** 01 — 建立统一套餐可售决策与真实历史资格 + +**Status:** ready-for-agent + +**架构通道:** Query。 + +**完整业务边界:** 本票只迁移和收口 `GET /api/c/v1/asset/info` 中当前套餐的读取与 DTO 投影,新增稳定 `current_package_id` 及购买资格契约。明确不修改资产实时状态、钱包、Gateway 刷新或订单流程,不实现前端页面。 + +- [ ] 查询继续执行个人客户认证、资产解析、当前有效绑定和当前 generation 校验,不允许上一世代或其他客户的套餐事实进入投影。 +- [ ] 当前套餐区域增加稳定 `current_package_id`、`can_purchase`、`purchase_mode` 和 `disabled_reason`;续费命令依赖套餐 ID,不使用 `current_package_usage_id` 作为授权凭证。 +- [ ] 当前渠道正常在售且满足既有规则时返回 `can_purchase=true, purchase_mode=normal`;渠道下架且历史资格成立时返回 `can_purchase=true, purchase_mode=renew_only`。 +- [ ] 套餐禁用、历史资格不存在、渠道未授权、价格异常或既有组合规则不成立时返回 `can_purchase=false, purchase_mode=disabled` 及稳定中文原因。 +- [ ] 无当前套餐时保持现有空值兼容语义,不伪造套餐 ID 或续费入口,并返回明确不可购买决策。 +- [ ] HTTP 集成测试覆盖在售当前套餐、平台与代理渠道下架续费、禁用、无历史、退款、解绑、转手及 generation 变化,验证统一响应和字段枚举。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/04-package-history-renewal-projection.md b/.scratch/ur40-off-shelf-package-renewal/issues/04-package-history-renewal-projection.md new file mode 100644 index 0000000..9512acb --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/04-package-history-renewal-projection.md @@ -0,0 +1,19 @@ +# 04 — 套餐历史提供下架续费入口 + +**What to build:** 当前资产所有人查看套餐历史时,每条历史记录都能获得基于当前事实计算的购买资格。待生效、生效中、已用完和已过期套餐均可在符合条件时提供下架续费入口;同一套餐的多条历史记录展示一致决策。 + +**Blocked by:** 01 — 建立统一套餐可售决策与真实历史资格 + +**Status:** ready-for-agent + +**架构通道:** Query。 + +**完整业务边界:** 本票只迁移和收口 `GET /api/c/v1/asset/package-history` 的权限、分页、批量可售投影与 DTO 契约。明确不修改套餐使用状态、不把历史行作为写授权、不实现前端页面或跨世代历史续费。 + +- [ ] 查询继续限定当前资产与当前 generation,并保持现有套餐类型、状态筛选、分页、排序和统一错误格式。 +- [ ] 每条历史记录增加 `can_purchase`、`purchase_mode` 和 `disabled_reason`,现有 `package_id` 作为续费提交的稳定标识,`package_usage_id` 仅用于展示和追踪。 +- [ ] 状态为待生效、生效中、已用完或已过期的真实购买记录均可形成下架续费资格;已过期记录具有可实际消费的 `renew_only` 契约。 +- [ ] 同一套餐存在多条历史记录时,各条记录基于套餐级当前购买资格返回相同结果;即使某一行自身已失效,只要同套餐另有有效历史,仍按统一当前决策投影。 +- [ ] 套餐当前正常在售时返回 `normal`;当前渠道下架且资格成立时返回 `renew_only`;禁用、无有效历史、未授权或价格异常时返回 `disabled` 和稳定原因。 +- [ ] 套餐、渠道分配和历史资格按当前页套餐 ID 批量读取或使用等价集合查询,同套餐复用决策结果,不产生逐行或逐套餐 N+1。 +- [ ] PostgreSQL 与 HTTP 集成测试覆盖四种有效历史状态、已失效、软删除、退款撤销、同套餐多记录、不同套餐、不同资产、不同世代、分页筛选及查询次数边界。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/05-client-order-renew-only-write-validation.md b/.scratch/ur40-off-shelf-package-renewal/issues/05-client-order-renew-only-write-validation.md new file mode 100644 index 0000000..939ed2a --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/05-client-order-renew-only-write-validation.md @@ -0,0 +1,20 @@ +# 05 — C 端创建订单按写时事实完成下架续费 + +**What to build:** 当前个人客户可以继续使用现有创建订单接口,为自己当前持有资产购买正常在售套餐,或续费同资产、当前世代、同套餐的历史下架套餐。下单始终按最新归属、渠道、历史和价格重新判定,展示后的任何事实变化都会安全拒绝过期资格。 + +**Blocked by:** 01 — 建立统一套餐可售决策与真实历史资格 + +**Status:** ready-for-agent + +**架构通道:** 主通道为复杂写,现有订单 Application/Service 调用 Domain Policy;旧 Service 仅作为当前完整用例的迁移门面。 + +**完整业务边界:** 本票收口 `POST /api/c/v1/orders/create` 的个人客户套餐购买写用例,将下架续费纳入现有订单、支付、实名、幂等和套餐组合流程。明确不新增续费 API、续费订单类型、资格字段、资格缓存或第二套幂等机制,不迁移未触碰的后台订单、自动购包、支付回调、退款和佣金流程。 + +- [ ] 请求继续只接受资产标识、套餐 ID 列表及现有支付相关字段,不新增或信任 `renewal`、客户 ID、资产类型、店铺、世代、上下架状态或价格字段。 +- [ ] 写入前重新解析当前登录客户、资产当前有效绑定、资产类型与 ID、当前 generation、当前销售渠道、套餐、渠道分配、真实历史资格和当前价格,不信任查询接口缓存结果。 +- [ ] 每一个请求套餐都调用统一策略;正常在售套餐按 `normal` 继续购买,下架套餐只有个人客户本人在对应资产上得到 `renew_only` 时通过。 +- [ ] 下架续费仍完整执行系列、赠送套餐、主套餐/加油包组合、实名顺序、强充、支付、订单状态和现有创建防重规则,不因历史资格绕过任何既有不变量。 +- [ ] 平台自营按当前套餐有效零售价创建价格快照;代理渠道按当前有效分配零售价创建快照并校验不低于授权成本,不读取历史使用记录或历史订单价格。 +- [ ] 套餐展示后被禁用或下架、代理分配被禁用/删除、资产解绑或转手、generation 变化、历史退款/失效及价格变为非法时,创建订单按最新事实拒绝并返回统一中文业务错误。 +- [ ] 可售结果向后续订单编排保留 `purchase_mode`、资产世代、套餐、销售渠道和命中历史资格的安全摘要,供成功审计接缝消费,但不向请求方暴露敏感历史明细。 +- [ ] 单元及 HTTP/PostgreSQL 集成测试覆盖正常新购、平台与代理下架续费、伪造续费字段、展示后事实变化、当前价格、组合规则、实名、并发重复提交和事务失败不留部分订单。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/06-non-personal-entrypoint-shelf-enforcement.md b/.scratch/ur40-off-shelf-package-renewal/issues/06-non-personal-entrypoint-shelf-enforcement.md new file mode 100644 index 0000000..f575224 --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/06-non-personal-entrypoint-shelf-enforcement.md @@ -0,0 +1,19 @@ +# 06 — 封堵后台代购与自动购包的下架绕过 + +**What to build:** 平台后台订单、代理代购和充值后自动购包等现有非个人续费入口,都会使用统一套餐可售策略按写时事实校验。它们可以继续购买符合原有规则的正常在售套餐,但遇到下架套餐时固定拒绝,不能借用个人客户的历史续费资格。 + +**Blocked by:** 01 — 建立统一套餐可售决策与真实历史资格 + +**Status:** ready-for-agent + +**架构通道:** 主通道为复杂写,辅助通道为 Infrastructure Worker。 + +**完整业务边界:** 本票只改造仓库中已经存在的平台后台普通订单、代理代购和自动购包入口,使其消费同一策略并关闭下架绕过。明确不实现 UR#36 批量订购、不迁移其他订单、钱包或佣金用例、不允许非个人主体进入 `renew_only`。 + +- [ ] 后台单笔、平台代购、代理代购和自动购包使用显式 actor 与 operation context 调用统一策略,不再通过绕过代理渠道校验、强制平台渠道或只加载套餐价格的方式跳过上下架规则。 +- [ ] 所有非个人续费场景遇到当前渠道下架套餐固定返回 `disabled`;即使资产存在同套餐有效历史,也不能获得 `renew_only`。 +- [ ] 套餐全局禁用、代理分配缺失/禁用/软删除、价格异常、赠送限制及现有主套餐/加油包组合规则在各入口得到一致执行。 +- [ ] 正常上架的平台和代理渠道后台购买、代购及自动购包保持原业务语义、结算方式和价格来源,不因本需求扩大迁移范围。 +- [ ] 自动购包任务在实际扣款和创建订单前按最新资产、generation、渠道、套餐及价格重新校验,不能使用充值单创建时的旧资格快照放行。 +- [ ] 各入口返回或记录统一安全业务原因,Handler、Worker 和 Service 不向客户端或日志直接暴露底层数据库错误。 +- [ ] 测试形成入口矩阵,验证个人 C 端下架续费可通过,而平台后台、平台代理、代理代购和自动购包对同一下架套餐全部拒绝,同时正常上架路径不回归。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/07-renew-only-audit-events.md b/.scratch/ur40-off-shelf-package-renewal/issues/07-renew-only-audit-events.md new file mode 100644 index 0000000..d2a2482 --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/07-renew-only-audit-events.md @@ -0,0 +1,22 @@ +# 07 — 接入下架续费成功与拒绝审计 + +**What to build:** 下架续费订单成功创建后,审计人员可以通过统一 Audit Event 追踪购买模式、资产、世代、套餐、渠道和历史资格摘要;构造请求因资格、归属或渠道事实被拒绝时,也能看到稳定失败原因,且审计中不包含客户敏感明细。 + +**Blocked by:** + +- 05 — C 端创建订单按写时事实完成下架续费 +- 06 — 封堵后台代购与自动购包的下架绕过 +- `.scratch/tech-global-audit/issues/01-audit-event-write-loop.md` — 01 — 交付不可变 Audit Event 写入闭环 + +**Status:** ready-for-agent + +**架构通道:** Application + Audit Port/Adapter。 + +**完整业务边界:** 本票只把 UR#40 的成功 `renew_only` 决策和被拒绝的构造购买请求接入公共 Audit Event,不创建独立续费日志表,不迁移套餐上下架、普通订单或其他旧审计日志,不用审计替代订单和套餐使用业务事实。 + +- [ ] 成功创建下架续费订单时写统一成功事件,至少记录 `purchase_mode=renew_only`、资产类型与 ID、当前 generation、套餐 ID、平台或代理销售渠道、订单引用及命中历史资格的脱敏摘要。 +- [ ] 成功事件与订单事实遵循公共 Audit Writer 的事务契约;审计失败时不得留下缺失必需审计的错误成功事实。 +- [ ] 归属伪造、跨资产/跨世代历史、下架非个人购买、套餐禁用、渠道未授权、退款失效及价格异常等拒绝结果,按公共失败审计规则使用独立短事务保存稳定动作、结果和业务失败分类。 +- [ ] 审计资源能够串联客户购买动作、资产、套餐、订单及销售渠道,但不记录手机号、OpenID、完整请求体、历史订单金额、底层错误或其他客户敏感明细。 +- [ ] 普通 `normal` 购买沿用现有审计语义,不因本票被重复记录为下架续费;后台与自动购包拒绝事件能够明确表明其 operation context 不具备历史续费例外。 +- [ ] PostgreSQL 集成测试覆盖成功事务一致性、业务回滚、拒绝事件独立落库、重复请求、脱敏与资源关联,并验证审计失败策略符合公共契约。 diff --git a/.scratch/ur40-off-shelf-package-renewal/issues/08-contract-release-and-entrypoint-acceptance.md b/.scratch/ur40-off-shelf-package-renewal/issues/08-contract-release-and-entrypoint-acceptance.md new file mode 100644 index 0000000..4b35ed0 --- /dev/null +++ b/.scratch/ur40-off-shelf-package-renewal/issues/08-contract-release-and-entrypoint-acceptance.md @@ -0,0 +1,30 @@ +# 08 — 完成接口契约、跨入口验收与发布门禁 + +**What to build:** 发布负责人可以通过自动化测试、OpenAPI、中文联调说明和发布检查,验证套餐商城不暴露下架套餐,当前套餐与套餐历史正确提供续费资格,个人客户可以按当前价格续费,而后台、代理、自动购买和批量订购均无法绕过下架限制。前端仅消费后端资格契约,不承担资格推导。 + +**Blocked by:** + +- 02 — 套餐商城只展示正常在售套餐 +- 03 — 当前套餐提供下架续费入口 +- 04 — 套餐历史提供下架续费入口 +- 05 — C 端创建订单按写时事实完成下架续费 +- 06 — 封堵后台代购与自动购包的下架绕过 +- 07 — 接入下架续费成功与拒绝审计 +- `.scratch/ur36-bulk-package-purchase/issues/04-unified-admin-package-purchasability.md` — 04 — 统一后台与批量入口的套餐可售策略 + +**Status:** ready-for-agent + +**架构通道:** 主通道为 Infrastructure,辅助通道为 Query 与 Application 验收。 + +**完整业务边界:** 本票收口 UR#40 的后端接口契约、真实链路验收、性能验证、中文功能总结、README、发布与回滚门禁。本仓库不实现前端页面;前端内容只描述如何消费 `can_purchase`、`purchase_mode`、`disabled_reason` 和 `current_package_id` 以及资格失效后的刷新交互,不扩展为跨仓前端任务。 + +- [ ] OpenAPI 完整描述套餐商城、资产信息、套餐历史和创建订单的最终契约,包含 `current_package_id`、`can_purchase`、`purchase_mode=normal|renew_only|disabled`、稳定中文原因及统一响应包装,生成校验无漂移。 +- [ ] 真实 Fiber、认证、Handler、Query/Application、GORM/PostgreSQL 集成验收覆盖商城永不返回下架套餐、当前套餐下架续费、待生效/生效中/已用完/已过期历史续费、禁用和无历史资格。 +- [ ] 入口矩阵验证个人 C 端可以续费,而平台后台普通订单、平台或代理代购、自动购包及 UR#36 批量订购对同一下架套餐全部拒绝;各入口正常上架购买不回归。 +- [ ] 竞态验收覆盖展示后套餐禁用、渠道分配禁用/删除、资产解绑/转手、generation 变化、历史退款/失效和价格非法,创建订单始终按写时事实拒绝。 +- [ ] 价格验收证明平台和代理渠道使用下单时当前零售价,不复用历史价格;代理零售价低于当前成本时拒绝。 +- [ ] PostgreSQL 查询计划验证历史资格使用适用索引,商城与历史列表批量投影没有逐行查询;目标接口满足项目分页与性能要求。 +- [ ] 后端联调文档明确前端只根据资格字段渲染:商城只展示后端返回项,`normal` 使用原动作,`renew_only` 显示“续费”和下架提示,`disabled` 隐藏或禁用并展示原因;提交只携带资产标识和套餐 ID,失败后展示后端中文错误并刷新数据。 +- [ ] 中文功能总结和 README 说明策略边界、接口契约、错误闭环、索引或迁移结论、测试方式、监控、发布顺序和回滚限制;不承诺前端页面实现。 +- [ ] 发布前只读核验套餐与代理分配状态、当前世代使用记录、订单退款一致性及查询计划;发现退款完成但使用记录仍有效的异常时必须修复或由策略显式排除后才能发布。 +- [ ] 回滚只恢复“下架全部拒绝”的应用行为,保留已合法产生的订单、套餐使用和审计事实,不回填资格、不删除历史数据,也不反向修改套餐上下架状态。