From 1657e21b5f4c4d8e784cbc8439e65afd45546162 Mon Sep 17 00:00:00 2001 From: figmar Date: Fri, 18 Sep 2026 21:38:26 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"docs:=20=E6=96=B0=E5=A2=9E=20apes-Aut?= =?UTF-8?q?hrix=20=E5=B9=B3=E5=8F=B0=E6=BC=94=E8=BF=9B=20PRD=20v2=EF=BC=88?= =?UTF-8?q?6=20Epic=20=C3=97=203=20Release=EF=BC=89"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 0e03977a7cc829848ec95529f2a22c9041d45e6c. --- README.md | 1 - apes-Authrix-平台演进PRD-v2.md | 605 --------------------------------- 2 files changed, 606 deletions(-) delete mode 100644 apes-Authrix-平台演进PRD-v2.md diff --git a/README.md b/README.md index 006f15b..de6e460 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,3 @@ ## 目录 - [apes-Arise × Casdoor 融合方案](./apes-Arise-Casdoor融合方案.md) — 以 Casdoor 为底座融合 apes 业务账权能力的总体方案(v0.1) -- [apes-Authrix 平台演进 PRD v2](./apes-Authrix-平台演进PRD-v2.md) — 基于仓库代码深读的工程交付 PRD:鉴权加固→配额商用闭环→登录生产化→AI 运行层,6 Epic × 3 Release(2026-09-18) diff --git a/apes-Authrix-平台演进PRD-v2.md b/apes-Authrix-平台演进PRD-v2.md deleted file mode 100644 index 3fd4788..0000000 --- a/apes-Authrix-平台演进PRD-v2.md +++ /dev/null @@ -1,605 +0,0 @@ -# PRD — apes-Authrix 统一权限中台演进(Platform Evolution v2) - -- **生成时间**:2026-09-18T21:15:00+08:00 -- **数据源**:代码仓库深度解读(私有仓 `apes-Arise/apes-Authrix`,Go 1.25 + Casdoor v4.0.0 底座)——README(25K,15 节权威文档)+ `extension/` 全部源码 + `contracts/` 4 份契约稿 + `web3/` 前端 + `deploy/`/`scripts/` + `docs/` 架构图(architecture/dataflow/workflow) -- **消费方**:人类工程团队 + AI coding agent 兼顾(验收标准含 Given/When/Then 与代码落点) -- **完整度**:moderate(Personas / Success Metrics 为代码角色与工程估算推断,无业务方确认,见文末 thin_sections) -- **版本说明**:本 PRD **替换**此前的 `apes-authon-platform-evolution.md`(基于 Spring Boot 三仓的旧版)——旧版描述的 apes-Authon-Server/Web 体系已按《apes-Arise × Casdoor 融合方案》降级为能力参考实现与交互蓝本,运行时底座切换为 Casdoor + Go 扩展层 - ---- - -## 1. Summary - -本 PRD 把 apes-Authrix(Casdoor 身份底座 + Go 扩展层 + web3 前端的统一权限中台)从「工程骨架完成的 MVP」推进到「可安全投产的商用账权平台」:修复系统性鉴权缺失(当前 30+ 个扩展层端点中仅 1 个受保护,绕过网关直连 :8001 即可无凭证读写全部租户的配额/套餐/组织/AI 配置数据)、清理四处明文凭证(含前端硬编码的 client_secret)、把配额中心从裸接口升级为挂在业务 API 上的强制扣减中间件、将短信复合登录从 STUB 变为可上线的真实通道、并按契约稿补齐 E9 AI 能力(Skill/对话/用量计量),整合为 6 个 Epic、3 个 Release 的演进计划,交付给 apes-Arise 平台工程团队(人与 coding agent 协同实现)。为什么是现在:P0-P5 全阶段刚于 2026-09-18 合入 main(PR #1),骨架新鲜、契约文档齐全,正是把「能跑」变成「敢用」的窗口期。成功长什么样:90 天内受保护端点覆盖率 100%、绕网关直连不可用(401)、配额扣减真实拦截业务调用、复合登录可发真实短信上线,apes-Authrix 成为多产品线可复用的认证授权矩阵。 - ---- - -## 2. Background & Problem - -### 问题(用户视角:apes-Arise 平台工程师 + 多产品线开发者) - -「我们的权限中台 apes-Authrix 刚跑通全部 P0-P5 阶段——Casdoor 做身份底座、Go 扩展层承载配额/套餐/组织/AI 配置、web3 前端 10 个页面、docker-compose 一键起。但当我准备把它接给业务系统时发现:扩展层 30 多个 API 里只有管理员租户视图一个端点校验身份,其余全部裸奔——我在浏览器里直接 curl :8001 就能改掉任何租户的配额、删掉任何部门的成员、读走 LLM 配置列表;配额中心有 Consume 接口但没有任何业务 API 调它,超限形同虚设;短信服务商的 aliyun/tencent 实现只打日志不发真短信。」 - -### 当前 workaround 及为什么不够好 - -- **鉴权**:唯一受保护端点 `GET /api/apes/admin/tenants` 用 Bearer token 回查 Casdoor `/api/userinfo`(fail-closed,设计正确);其余端点靠「网关不暴露 8001 端口」的部署约定——但 docker-compose 恰恰把 8001/8000 都映射到了宿主机,且网关(goproxy/nginx.conf)是纯反代零校验。 -- **租户隔离**:org 维度全靠调用方自报 `?casdoorOrgId=`/`?ownerId=` 参数;`assertDeptInOrg` 只覆盖 ListPositions/ListMembers,`GetDepartment/UpdateDepartment/DeleteDepartment/GetMember` 持 id 即可跨租户操作(service.go:310-481)。 -- **配额**:`POST /api/apes/quota/consume` 是裸接口且无周期重置任务;E2 套餐绑定时下发的 QuotaSpec 只在绑租户那一刻生效,之后没有任何业务路径消费它。 -- **AI 能力**:E9 只到配置管理层(LLM+Agent 两表 CRUD + 探活),契约稿 contract-e9-ai-llm.md 设计的 Skill、对话调试、用量看板、模型对比、提示词库全部未实现;`LLM_SECRET_SOURCE=vault` 是空壳,`LLM_MODULE_ENABLED` 开关无效(路由无条件注册)。 - -### 为什么是现在 - -1. **P0-P5 刚全部完成**(2026-09-18 PR #1 合入,开发历史被 squash):契约文档(4 份 contracts/)、验收脚本(6 个 verify-*.sh)、单测(sms/composite_login)骨架都在,补安全的边际成本最低。 -2. **距离「可接入业务」只差最后一层**:REST/Webhook/数据/前端四重契约已验证,Casdoor 一键升级机制(upgrade.sh/compat-check.sh)已设计——唯独鉴权中间件与网关校验缺失,这是接入前的唯一硬阻塞。 -3. **README 第 15 节已自认约束清单**:短信码明文、Webhook 无签名、SQLite 单写者、Skill 未实现、管理员鉴权无缓存——加上代码走查发现的明文凭证与 STUB,债务清单完整且都在可控范围。 -4. **旧 Spring Boot 体系已定位**:apes-Authon-Server/Web 降级为参考实现/蓝本,组织不会在两条技术线上重复投入——演进焦点唯一。 - -### 不解决什么(本 PRD 故意排除) - -- **Casdoor 二次开发**:铁律 1「Casdoor 永不修改」——所有需求在扩展层/网关解决,不为任何功能破例打 patch(patches/ 保持为空)。 -- **E10 物理分库/多 schema**:README 已明确放弃跨实例分库、远期走同 Postgres 实例多 schema——本 PRD 只做性能预研,不做迁移(列 Phase 2)。 -- **E8 用户偏好**:融合方案中的 E8 至今无表无代码无页面,需求未定义——不纳入本 PRD。 -- **旧 apes-Authon-Server/Web 的任何修复**:JDT/权限过滤/AI 提取等旧 PRD Epic 全部作废,旧仓只读参考。 - ---- - -## 3. Personas & User Segments - -> ⚠️ 本节 personas 由仓库代码角色与文档描述推断,未经真实用户访谈确认。 - -### Primary Persona — 平台工程师 - -``` -姓名:陈屿 -身份:apes-Arise 团队 Go 工程师,负责 apes-Authrix 演进 -情境:P0-P5 骨架刚合入 main,要在其上补齐鉴权/配额联动/登录生产化,然后接给业务系统 -JTBD:当多产品线需要统一认证与配额管控时,陈屿想在一个契约清晰、可跟随 Casdoor 官方升级的仓库里 - 完成安全加固与能力补齐,从而不必担心每次官方发版产生合并冲突或安全债务。 -当前 workaround:靠部署约定(不暴露 8001)和「内部系统暂时没人攻击」的侥幸心理 -痛点:① 鉴权中间件只覆盖 1/30+ 端点,接入业务前不敢开放;② 前端 config.js 硬编码 client_secret, - 无法开源/外包;③ compat-check.sh 硬编码他人机器路径,CI 不可用 -``` - -### Secondary Persona — 业务系统开发者(多产品线接入方) - -``` -姓名:林澜 -身份:伙伴云/交易平台等业务系统的后端开发者 -情境:产品线要接 apes-Authrix 做登录、组织、配额控制 -JTBD:当业务系统需要认证与配额能力时,林澜想通过网关一个入口 + 标准 OIDC/Bearer token 完成接入, - 从而不必关心 Casdoor/扩展层的内部拓扑。 -当前 workaround:直连 :8001 裸调接口(当前竟真的能通) -痛点:① 不知道哪些端点需要 token、哪些不需要;② 配额不真实生效,业务侧不敢依赖它做限流; - ④ 想给 AI 能力做用量管控,但 E9 没有计量 -``` - -### Secondary Persona — 租户管理员(SaaS 客户) - -``` -姓名:王慧 -身份:购买套餐的企业客户 IT 管理员,经 web3 控制台管理组织 -JTBD:当企业需要控制「成员归属哪个部门、AI 用量多少」时,王慧想在 web3 完成组织/配额/AI 配置管理, - 从而不需要找供应商人工操作。 -当前 workaround:web3 有 10 个页面但 E6 组织架构、E3 伙伴云、E4 AccessToken 均无页面,只能找工程师裸调 API -痛点:看不到自己租户的组织树;配额页面显示的数字不反映真实消耗 -``` - -### 这个产品不为谁做 - -apescale 终端 C 端用户与各业务系统的业务功能——Authrix 只做「你是谁(借 Casdoor)、你能用多少(配额/套餐)、你属于哪个组织(org)、你配了哪些 AI 能力(E9)」,业务逻辑永远在业务系统里。 - ---- - -## 4. Goals & Success Metrics - -### 产品目标(outcome-framed) - -让 apes-Authrix 从「能跑的工程骨架」变成「多产品线敢接入的商用账权中台」——具体表现为:任何未鉴权请求都进不来、配额真实拦截调用、登录通道可上线、AI 能力有计量。 - -### 成功度量表 - -| 指标 | 度量什么行为 | 目标值 | 时间窗 | -| --- | --- | --- | --- | -| **受保护端点覆盖率**(primary) | 扩展层需身份的端点挂鉴权中间件的比例(走查清单逐个核对) | 30+/30+ = 100% | Release 1 上线即达成 | -| **越权攻击成功率**(primary) | 持有效 token 的 A 租户用户访问 B 租户资源(org 过滤盲测用例集) | 0 | Release 1 上线即达成 | -| **直连绕过可用性**(primary) | 无 token 直达 :8001 调用任意 /api/apes/* 端点 | 全部 401(且生产环境 8001 不暴露宿主机) | Release 1 | -| **配额拦截真实生效**(secondary) | 配额超限后业务调用被 402 拒绝的比例(对照 E1 Consume 日志) | 100%(超限调用必被拒) | Release 2 后 30 天 | -| **复合登录可用性**(secondary) | 真实短信通道下发→登录成功率(灰度租户) | ≥95%(排除用户输错) | Release 2 后 30 天 | -| **E9 计量覆盖**(health) | Agent 对话调用产生配额消耗记录的比例 | 100% 调用有计量 | Release 3 后 30 天 | -| **契约回归通过率**(health) | compat-check.sh 在干净 CI 环境跑通 | 100%(可移植化后) | Release 1 | - -> ⚠️ 目标值为工程估算建议值,需各产品线负责人确认后锁定。 - -### Anti-metrics(反指标——明确不优化) - -- **不优化「接口响应速度」到引入本地 JWT 验签的程度**:README 鉴权设计明确「回传 Casdoor userinfo 换身份,不自己解 JWT、不自己验签名,避免密钥分叉」——短期缓存可以(README 已列为后续项),但禁止为了性能把验签密钥散布到扩展层,那是用架构债换毫秒。 -- **不优化「端点数量」**:E9 契约稿里的对话调试台/模型对比/提示词库按需排期,禁止为了「功能全」一次铺开——首发最小子集的纪律正是这个项目活下来的原因。 -- **不优化「单测行数」**:只补鉴权/配额/org 过滤等安全关键路径的测试,不为覆盖率数字写空转测试。 - -### 关键假设 - -**至少一个 apescale 产品线(伙伴云/交易平台等)在 6 个月内会把生产流量接入 apes-Authrix。** 如果错了——即产品线继续用旧单体账权——本 PRD 的 Epic E(前端补页)/F(性能预研)失去消费方,应收缩为 Epic A+B+C+D 的自用安全闭环。验证方式:Release 1 启动前拿到至少 1 个产品线负责人的书面接入意向。 - ---- - -## 5. Value Proposition - -**对平台工程师(陈屿)**: - -> 陈屿终于可以在一个零 Casdoor 侵入、契约测试护航的仓库里交付安全加固,而不必在「改官方源码」和「留安全洞」之间二选一——因为四重契约(REST/Webhook/数据/前端)+ submodule 锁 tag + upgrade.sh 门禁已经把「跟随官方」的路铺好,本 PRD 只需把缺失的鉴权中间件、网关校验、配额联动补进扩展层自有代码。 - -**对业务系统开发者(林澜)**: - -> 林澜终于可以通过网关单一入口 + 标准 Bearer token 接入全部账权能力,而不必打听哪些端口能直连——因为鉴权统一后,所有 /api/apes/* 端点行为一致:带有效 token 即可用、越权即 403、超配额即 402,配额数字第一次变得可信。 - -**对租户管理员(王慧)**: - -> 王慧终于可以在 web3 控制台看到自己租户的完整视图(组织树/配额消耗/AI 用量),而不必找工程师裸调 API——因为 E6/E4 前端页面补齐后,管理动作全部走受鉴权的正规通道。 - -### 竞争差异化(对比「直接用 Casdoor」和「回旧 Spring Boot 单体」两个真实对手) - -- **对手做对了什么**:Casdoor 原生身份/协议能力极强且免费;旧单体功能全、业务方熟悉。 -- **对手留下的空白**:① Casdoor 没有租户级套餐/配额/组织架构/AI 配置这些**业务账权**能力;② 旧单体无法跟随任何上游升级(apes-commons 黑盒 JAR)、无法独立部署、AI 后端从未提取。 -- **本平台填的是哪个空白**:唯一同时满足「身份能力借力开源最强底座(可持续升级)+ 业务账权自主可控(apes_ 表零外键)+ AI 能力独立供给」的中间层——Casdoor 给不了的配额商用闭环和旧单体给不了的可演进性,正是 Authrix 的存在理由。 - ---- - -## 6. Solution & Feature Scope - -> Story 来源:README 第 15 节「已知约束与后续项」+ 4 份契约稿与代码现状的差距走查 + 安全审计发现。设计资产为仓库中已存在的代码/契约稿/前端页面路径,工程可直接对照实现。 - -### Epic A — 鉴权与安全加固(P0,阻塞一切对外接入) - -#### A1 扩展层统一鉴权中间件全覆盖 - -**Persona**:陈屿(平台工程师)/ 林澜(业务接入方) -**Job**:任何 /api/apes/* 请求必须持有效身份,org 越权必须被拒 -**优先级**:P0 -**关键假设标记**:⭐(鉴权模型若与 Casdoor userinfo 性能冲突,需引入缓存设计——影响全平台 API 延迟基线) - -**功能描述**:当前 `auth.go` 的 `currentUser()/isAdminUser()` 只被 handlers_e10.go 的 1 个端点使用,其余 quota/plan/department/dept/llm/agent/access-token/huoban/op-log 等端点零鉴权,org 维度全靠调用方自报参数。做一套 gin 中间件:从 Bearer token 回查 Casdoor userinfo(沿用既有设计)→ 注入 user/org 上下文 → 按「用户身份推导 casdoorOrgId」替代自报参数 → 服务层 org 过滤强制化。 - -**In Scope(这版要做的)**: -- gin 群组中间件 `requireAuth()` 挂到全部业务路由(`server.go` 中除 `/healthz` 与 `/api/apes/webhook/casdoor`、`/api/apes/auth/*` 登录前置端点外全挂) -- `requireAuth()` 内置 org 上下文注入:从 userinfo 的 organization/owner 声明取 org,拒绝请求参数中自报的 `casdoorOrgId` 与身份不符的组合(管理员例外) -- 补齐 `GetDepartment/UpdateDepartment/DeleteDepartment/GetMember` 的 `assertDeptInOrg`/`assertMemberInOrg` 归属校验(当前仅 ListPositions/ListMembers 有,service.go:310-481) -- webhook 端点强制校验 `WEBHOOK_SECRET`(HMAC-SHA256)——当前默认空串即跳过校验,改为空串时启动报错 -- 鉴权失败统一错误结构 `{ "error": "unauthorized" }` / 403 `{ "error": "forbidden: cross-tenant access" }`,与既有 gin.H 风格一致 -- E6 全部端点 `?casdoorOrgId=` 从「必填自报参数」改为「服务端注入、参数仅管理员可用」的兼容双轨(一个 Release 后删自报) - -**Out of Scope(这版故意不做的)**: -- userinfo 短期缓存(README 后续项,量上来再做——先验证正确性) -- API 级细粒度 RBAC(Casbin 已在 Casdoor 侧,扩展层只做身份+org,不做权限点) - -**验收标准(Given / When / Then)**: -- Given 无 token 请求 `GET /api/apes/quota`,When 直达 :8001,Then 401 `{"error":"unauthorized"}` -- Given A 租户用户(有效 token,org=org-a)请求 `GET /api/apes/department?casdoorOrgId=org-b`,When org 不匹配,Then 403 cross-tenant access -- Given A 租户用户持 B 租户的 departmentId 调 `PUT /api/apes/dept/:id`,When 归属校验执行,Then 403(当前现状:200 直接改掉) -- Given 管理员(isAdmin=true 或命中 ADMIN_USER_IDS),When 带任意 orgId 查询,Then 放行(管理员跨租户视图保留) -- Given WEBHOOK_SECRET 为空,When 服务启动,Then 启动失败并提示配置缺失(不再静默跳过签名校验) -- 边缘情况:Casdoor 不可达时全部受保护端点返回 502 `casdoor_unavailable`(fail-closed,不放行) -- 空状态:token 过期 → 401;userinfo 返回 org 为空 → 拒绝并提示租户身份缺失 -- 回归:`deploy/test-extension.sh` 冒烟脚本同步补 401/403 断言 - -**设计触点**: -- 涉及屏:web3 全部 10 页(auth.js 的 api() 已自动附 Bearer,前端零改动即通过) -- 涉及组件:`extension/internal/httpapi/auth.go`(中间件)、`server.go`(路由挂载)、全部 `handlers_*.go` -- 涉及状态:401 跳登录、403 越权提示、502 Casdoor 不可用提示 - -**关联页面/代码落点**: -- `extension/internal/httpapi/auth.go` — 中间件主体(现有 currentUser/isAdminUser 逻辑迁入) -- `extension/internal/httpapi/server.go` — 路由群组挂载(:165 附近 webhook 例外) -- `extension/internal/service/service.go:310-481` — dept/member 归属校验补齐 -- `web3/src/auth.js` — 401 自动跳 OIDC 重新登录(现有逻辑确认覆盖) - -**已生成的设计资产**: -- `extension/internal/httpapi/handlers_e10.go` — 唯一已正确鉴权的端点(作为中间件实现的参考范式) -- `contracts/contract-baseline.md` — 四重契约基线(REST 契约边界) -- `docs/apes-Authrix.dataflow.json/.html` — 「Bearer→userinfo」数据流图(README 声称的全局设计,本 Story 使其成真) - -**待澄清问题**: -- E7 登录前置端点(sms-code/send)是否需要 IP 级限频(当前已有单号维度限频)? -- 存量调用方(若有)依赖自报 orgId 的迁移窗口给多长? - -#### A2 网关鉴权与端口收敛 - -**Persona**:陈屿 -**Job**:生产环境绕过网关的路径不存在 -**优先级**:P0 - -**功能描述**:gateway(goproxy/main.go 与 nginx.conf)当前是纯反向代理零校验,docker-compose 把 8001/8000 直接映射宿主机——直连即绕过一切。网关层加 Bearer token 存在性预检(深度校验仍由扩展层回查 userinfo,网关只挡「无 token」的裸奔流量),生产 compose 收敛端口暴露。 - -**In Scope**: -- 网关对 `/api/apes/*`(除 webhook、healthz)校验 `Authorization: Bearer` 头存在性,缺失即 401 -- docker-compose 拆分 dev/prod 两个 override 文件:prod 不映射 8001/8000 到宿主机(仅容器网络内互通) -- 网关支持 `EXT_AUTH_EXEMPT_PATHS` 环境变量维护豁免清单 -- `deploy/verify-gateway.sh` 补「直连 :8001 无 token 被拒」「经网关无 token 被拒」两类断言 - -**Out of Scope**: -- 网关做 JWT 签名验证(鉴权事实源保持唯一在 Casdoor,避免密钥分叉——网关只做存在性预检) -- 网关级限流(Phase 2) - -**验收标准(Given / When / Then)**: -- Given 无 Authorization 头请求 `http://gw:8080/api/apes/quota`,When 经网关,Then 401(不转发到扩展层) -- Given 生产 compose(-f docker-compose.yml -f docker-compose.prod.yml),When 从宿主机 curl localhost:8001,Then 连接拒绝(端口未映射) -- Given 带 Bearer 的请求,When 经网关,Then 正常转发(存在性预检不误伤) -- 边缘情况:豁免清单内路径(webhook)无 token 仍可达(Casdoor webhook 无 token 语义) - -**设计触点**: -- 涉及组件:`gateway/goproxy/main.go`、`gateway/nginx.conf`(等价配置同步)、`deploy/docker-compose.yml` + 新增 `docker-compose.prod.yml` -- 涉及状态:网关 401 响应格式与扩展层一致 - -**关联页面/代码落点**: -- `gateway/goproxy/main.go` — 反代逻辑处加预检 -- `deploy/docker-compose.yml` — 端口映射收敛 - -**已生成的设计资产**: -- `deploy/verify-gateway.sh` — 既有路由分流验证脚本(扩展断言) -- `docs/apes-Authrix.architecture.json` — 三服务拓扑图(本 Story 使 prod 拓扑与图一致) - -**待澄清问题**: -- 生产部署形态是 compose 还是已有 K8s?决定端口收敛的落地方式 - -#### A3 明文凭证清零 - -**Persona**:陈屿 -**Job**:仓库与前端产物中不存在任何可用凭证 -**优先级**:P0 - -**功能描述**:四处明文凭证:① `web3/src/config.js` 硬编码 app-built-in 的 client_secret(打包进前端产物,公网可抓);② `deploy/verify-gateway.sh:44` 与 `verify-contract2.sh` 硬编码 admin/123 与 client_secret;③ 短信验证码明文落库(store.go:104-114,注释自认 MVP);④ `compat-check.sh:61-79` 硬编码他人机器路径(/Users/auth...)。全部清理。 - -**In Scope**: -- web3 走 Casdoor 标准 PKCE(public client,无 client_secret)或改由网关注入——二选一(见待澄清) -- 验证脚本凭证全部改环境变量读取,`.env.example` 给模板 -- 短信码改 SHA-256 加盐摘要存储(README 后续项第 1 条),发送时同算法比对 -- compat-check.sh 路径参数化(`CASDOOR_SOURCE_DIR` 环境变量),删除硬编码 home 路径 -- 上线检查单:轮换已泄露的 client_secret 与 admin 初始密码 - -**Out of Scope**: -- 引入 Vault(LLM_SECRET_SOURCE=vault 的空壳实现属 Epic D) - -**验收标准(Given / When / Then)**: -- Given web3 构建产物 dist/,When `grep -r "80b07fed\|client_secret" web3/dist/`,Then 0 命中 -- Given 新用户注册收到短信码,When 查 apes_sms_code 表,Then code 列为 64 位摘要,明文不可见 -- Given 验证码校验,When 用户输入正确明文码,Then SHA-256(盐+明文) 比对通过、登录放行(端到端不变) -- Given 干净机器 clone 仓库,When `./scripts/compat-check.sh`,Then 不因路径报错(可移植) -- 边缘情况:存量明文验证码(迁移瞬间在途的码)统一作废重发,兼容期 ≤5 分钟(TTL 300s) - -**设计触点**: -- 涉及屏:登录相关(web3 OIDC 流程) -- 涉及组件:`web3/src/config.js`、`web3/src/auth.js`、`extension/internal/store/store.go:104-114`、`extension/internal/service/sms.go:148` -- 涉及状态:OIDC 授权跳转(PKCE 后无 secret 参与) - -**关联页面/代码落点**: -- `web3/src/config.js` — 硬编码 client_secret 所在 -- `extension/internal/service/sms.go` — 验证码生成与校验(散列化) -- `extension/internal/service/sms_test.go` — 既有单测同步改造 - -**已生成的设计资产**: -- `contracts/contract-e7-composite-login.md` — E7 设计稿(散列存储的契约依据) - -**待澄清问题**: -- web3 去 secret 方案:PKCE(标准,改动小)vs 网关 BFF 注入(更严,改动大)——建议 PKCE 先行 - ---- - -### Epic B — 配额商用闭环(P0/P1) - -#### B1 配额扣减中间件挂载业务 API - -**Persona**:林澜(业务接入方) -**Job**:超配额的调用被平台自动拒绝,业务侧不用自建限流 -**优先级**:P0(Release 2 首位) -**关键假设标记**:⭐(配额是否真的能作为业务限流的唯一依据——若 metric 粒度不够,需先扩 metric 定义) - -**功能描述**:E1 配额中心(Consume/Reset,超限返回 QuotaExceededError 映射 402)功能完整但只是裸接口——没有任何业务 API 调它,也没有周期重置任务。做一个声明式中间件 `RequireQuota(metric, ownerScope)`,挂到业务端点(E9 对话、后续业务 API)上,调用前扣减、超限拒绝;补周期重置(按 QuotaSpec.period:monthly/daily)的定时任务。 - -**In Scope**: -- `RequireQuota` gin 中间件(org 维度扣减,管理员豁免可选) -- 周期重置任务:启动时 + 定时(cron 语义)对 period 到期的 quota 行 Reset -- 402 响应带剩余量与重置时间(`X-Quota-Remaining` / `X-Quota-Reset-At` 头) -- web3 quota 页展示 used/limit/period 真实数据(现有页面已对齐端点,数据源补真) - -**Out of Scope**: -- 计费/账单(只管拦截不管收钱) -- user 级配额(首发 org 级,user/app 维度表结构已支持,按需再挂) - -**验收标准(Given / When / Then)**: -- Given 租户 T 配额 api_calls limit=100 used=100,When T 的用户调用挂了 RequireQuota("api_calls") 的端点,Then 402 + `X-Quota-Remaining: 0`,业务逻辑不执行 -- Given limit=100 used=99,When 调用成功,Then used=100(原子扣减,并发 50 请求不超发——沿用现有原子条件更新) -- Given period=monthly 的 quota,When 月度周期翻转,When 定时任务执行,Then used 归零且 op_log 留痕 -- Given 无 quota 记录的 metric(未配置),When 调用,Then 放行(limit null = 不限的语义保持) -- 边缘情况:扣减成功但业务 handler panic → 中间件 recover 回滚配额(或至少 op_log 记录差异) - -**设计触点**: -- 涉及屏:web3 `/quota` 页(用量真实性) -- 涉及组件:`extension/internal/service/service.go`(E1 Consume/Reset)、新 `httpapi/middleware_quota.go` -- 涉及状态:402 超额提示态(web3 api() 抛错路径已有) - -**关联页面/代码落点**: -- `extension/internal/service/service.go:111`(QuotaExceededError)、`handlers_p2.go:58`(现有裸接口) -- `web3/src/pages/quota.js` — 配额页 - -**已生成的设计资产**: -- `contracts/contract-e10-multi-tenant.md` — 租户×配额模型设计 -- `deploy/verify-p2.sh` — P2 配额/套餐验收脚本(扩展中间件断言) - -**待澄清问题**: -- 首批挂 RequireQuota 的端点清单(建议:E9 对话类 + 未来的业务代理端点;CRUD 类管理接口不挂) - -#### B2 套餐变更联动(增量配额) - -**Persona**:王慧(租户管理员) -**Job**:改套餐后配额立即按新套餐生效 -**优先级**:P1(Release 2) - -**功能描述**:E2 绑定套餐时把 QuotaSpec 下发为租户配额,但改绑(ON CONFLICT DO UPDATE)时对已有配额的处理未定义。定义升级语义:改绑时按新套餐 QuotaSpec 重设 limit(used 保留或清零,二选一见待澄清),并写 op_log。 - -**In Scope**: -- 改绑时配额重设逻辑 + op_log 留痕 -- web3 `/plan` 页补「改绑影响预览」(diff 旧新套餐配额) - -**验收标准**: -- Given 租户 T 已绑 plan-basic(api_calls=1000),When 改绑 plan-pro(api_calls=100000),Then T 的 quota 行 limit=100000,op_log 记录变更 -- Given 改绑到配额更小的套餐且 used 已超新 limit,When 下次调用,Then 402(语义见待澄清:是否立即拦截 vs 周期末) - -**已生成的设计资产**:`web3/src/pages/plan.js` — 套餐页(补预览交互) - -**待澄清问题**: -- 降档时 used > 新 limit 的处理策略(立即拦截 or 豁免到周期末)——需业务方定 - ---- - -### Epic C — E7 复合登录生产化(P1) - -#### C1 真实短信 Provider 实现 - -**Persona**:王慧 / 终端登录用户 -**Job**:手机真的能收到验证码 -**优先级**:P1(Release 2) - -**功能描述**:SMSProvider 的 aliyun/tencent 两个实现当前是纯 STUB(只打日志不真发),仅 log 实现可用(开发态)。按既有接口(`Send/Name`)补真实 SDK 调用;E2E 联调发送→校验→复合登录全链路。 - -**In Scope**: -- aliyun(dysmsapi)与 tencent(sms)真实实现,凭证走既有 `SMS_ACCESS_KEY/SMS_SECRET` env -- 发送失败重试一次 + 失败错误码透传(`sms_provider_error`) -- E2E 验证脚本 `deploy/verify-e7-sms.sh`(真发一条到测试号) - -**Out of Scope**: -- 国际短信(countryCode 归一化见 C2) -- 语音验证码 - -**验收标准(Given / When / Then)**: -- Given SMS_PROVIDER=aliyun 且凭证有效,When 请求 send,When 测试手机 60s 内收到短信,When 提交复合登录,Then 换取 RS256 JWT 成功 -- Given 凭证错误,When send,Then 5xx `sms_provider_error`(不暴露凭证细节)且不扣减每日限额 -- Given Provider 超时,When 重试一次仍失败,Then 返回 502 并落 op_log -- 回归:`sms_test.go` 既有单测全绿(STUB 不动,log provider 仍是默认) - -**关联页面/代码落点**:`extension/internal/service/sms.go`(Provider 实现处)、`config.go`(既有 env 全齐) - -**已生成的设计资产**:`contracts/contract-e7-composite-login.md`、`extension/internal/service/sms_test.go` + `composite_login_test.go` - -#### C2 E7 实现与契约稿对齐 - -**Persona**:陈屿 -**Job**:代码行为与设计稿一致,契约文档可信 -**优先级**:P1(Release 2,与 C1 并行) - -**功能描述**:走查发现实现与 contract-e7 有六处偏差,逐项对齐:① 无 E.164/countryCode 归一化(现只校验 11 位数字);② 无「查用户存在」步骤(现直接以 phone 作 username 走 password grant);③ send 响应缺 expiresIn/cooldown 字段;④ 错误响应用 `{error}` 而非契约约定的 `{status,msg,code}`;⑤ 契约承诺的过期码 24h 惰性清理未写;⑥ `user_not_found` 错误码定义了但未用。 - -**In Scope**:上述六项逐条对齐或修订契约(二选一:改代码向契约靠,或改契约承认现状——每项走待澄清决策);过期码清理任务(惰性删除)。 - -**验收标准**: -- Given 契约稿每条 MUST 级条款,When 逐条核对代码,Then 全部满足或契约已修订并在 PR 说明 - -**待澄清问题**:六处偏差逐项「改码 or 改契约」的决策表(建议:①②⑥ 改码——涉及安全与用户体验;③④⑤ 视前端依赖定) - ---- - -### Epic D — E9 AI 能力从配置层走向运行层(P1/P2) - -#### D1 Skill 配置(补契约稿最小缺口) - -**Persona**:陈屿 -**Job**:Agent 可挂载可复用的技能/提示词包 -**优先级**:P2(Release 3) -**关键假设标记**:⭐(技能体系是否真有使用方——若产品线只要 Agent+模型,Skill 应继续搁置) - -**功能描述**:contract-e9 设计了 `apes_skill_config` 表但首发未实现(README 已知约束第 4 条)。按契约稿补:建表(migrate() 追加,注意 `;\n` 分隔约定 + org 索引)、Agent-Skill 关联、CRUD 端点、web3 `/skill` 页(按第 14 节六步开发指南走)。 - -**验收标准**: -- Given Skill S(提示词包)创建并关联 Agent A,When 查看 A 详情,Then 关联回显 -- Given 迁移启动,When migrate() 执行,Then apes_skill_config 表建立且不影响既有 13 表 - -**已生成的设计资产**:`contracts/contract-e9-ai-llm.md`(表结构与端点设计)、`web3/src/pages/agent-detail.js`(关联展示参考) - -#### D2 Agent 对话调用与用量计量 - -**Persona**:林澜(业务接入方) -**Job**:业务系统经 Authrix 调 Agent 对话,用量计入租户配额 -**优先级**:P1(Release 3 核心) - -**功能描述**:当前 E9 只有配置 CRUD + 探活(`/llm/config/:id/test`),无对话调用端点。补 `POST /api/apes/agent/:id/invoke`(同步调用,返回模型输出 + token 用量),并挂 Epic B 的 `RequireQuota("llm_calls")` 中间件——AI 用量第一次进入配额管控。调用日志落 `apes_op_log`(或新表 `apes_llm_call_log`,见待澄清)。 - -**In Scope**: -- invoke 端点:取 Agent 配置 → 从 LLMSecretSource 解密钥 → 调 provider(默认 openai 兼容协议)→ 返回输出+用量 -- RequireQuota 挂载(B1 交付后) -- 用量记录(metric: llm_calls / llm_tokens) -- `LLM_MODULE_ENABLED` 开关修复(当前无效——路由无条件注册) -- `LLM_SECRET_SOURCE=vault` 空壳实现(env 先行,vault 接口留桩但标注未实现) - -**Out of Scope**: -- 流式 SSE(同步先行) -- 多模态、对话历史持久化(产品线需求明确后再排) - -**验收标准(Given / When / Then)**: -- Given 配置了有效 LLM key 的 Agent A,When 带 token 调 invoke,Then 返回模型输出 + `usage: {tokens}` -- Given 租户 T 的 llm_calls 配额用尽,When T 调 invoke,Then 402(B1 中间件拦截,模型不产生计费) -- Given LLM_MODULE_ENABLED=false,When 服务启动,Then /api/apes/llm* 与 /api/apes/agent* 路由不注册(404) -- 边缘情况:provider 超时(LLMTestTimeoutMS 语义扩展到 invoke)→ 504 明确错误码 -- 空状态:Agent 未绑 LLM 配置 → 400 `llm_config_missing` - -**设计触点**:web3 无新页面(invoke 是 API 能力);admin-tenants 页的 agentCount/llmCount 已有 - -**关联页面/代码落点**:`extension/internal/service/llm.go`(扩展 invoke)、新 `httpapi/handlers_e9_invoke.go` - -**已生成的设计资产**:`contracts/contract-e9-ai-llm.md`(对话调试台设计参考)、`web3/src/pages/llm-detail.js` - -**待澄清问题**: -- 调用日志独立建表(apes_llm_call_log)还是复用 op_log?建议独立(维度不同:token/耗时/成本) - -#### D3 E9 前端补页与对话调试台(可选) - -**Persona**:陈屿 -**Job**:web3 内直接调试 Agent 对话 -**优先级**:P2(Release 3 尾部,D2 交付后) - -**功能描述**:按 contract-e9 的对话调试台设计,在 web3 补 `/agent/:id/playground` 页(调用 D2 invoke,展示输入输出与用量)。沿用页面模块 `{ html, mount }` 约定 + main.js 双登记。 - -**验收标准**:Given Agent A,When 在 playground 输入 prompt 调用,Then 展示输出 + token 用量 + 配额余量 - -**已生成的设计资产**:`web3/src/pages/agent-detail.js`(详情页参考)、`contracts/contract-e9-ai-llm.md`(调试台设计稿) - ---- - -### Epic E — 工程化与前端补页(P2) - -#### E1 Webhook 能力补齐 - -**功能描述**:契约 2 只处理 user/login 事件,`update-application`/`update-organization` 分支未实现(server.go:165);op_log 可被重复投递重复写入(无幂等去重)。补两个事件分支 + 基于(event+对象 id+时间窗)的幂等去重。 - -**验收标准**: -- Given Casdoor 推送 update-organization 事件,When webhook 接收,Then op_log 落记录且语义正确 -- Given 同一事件重放 3 次,When webhook 接收,Then op_log 仅 1 条新增 - -**代码落点**:`extension/internal/httpapi/server.go:165` - -#### E2 web3 补 E6/E4/E3 管理页 - -**功能描述**:后端 E6 组织/E4 AccessToken/E3 伙伴云端点齐全但 web3 无页面(10 页清单不含)。按页面六步约定补 3 页:`/org`(部门树+岗位+成员,用 `?tree=true`)、`/tokens`(E4 令牌管理)、`/huoban`(E3 同步状态)。全部经 A1 鉴权中间件。 - -**验收标准**: -- Given 租户管理员登录 web3,When 访问 /org,Then 仅见本租户部门树(org 由中间件注入) -- Given /tokens 页创建令牌,When 调 verify 端点,Then 校验通过 -- 空状态:无部门时树形空态 + 「创建部门」引导 - -**已生成的设计资产**:`web3/src/pages/` 既有 10 页范式(如 quota.js 作为 CRUD 页模板) - -#### E3 测试与 CI 可移植化 - -**功能描述**:单测仅 2 个文件(sms/composite_login),service.go/llm.go/store/httpapi 全无测试;compat-check.sh 硬编码他人机器路径。补安全关键路径测试(A1 中间件、B1 配额原子性、org 过滤)+ CI 脚本可移植(A3 已参数化路径后接入 Gitea Actions 或 drone)。 - -**验收标准**: -- `go test ./...` 覆盖:鉴权中间件(401/403/管理员豁免)、配额并发原子性、dept 越权用例——全绿 -- 干净 CI 环境跑 compat-check.sh 通过 - -**待澄清问题**:CI 平台选型(Gitea Actions vs drone vs 手动门禁) - ---- - -### Epic F — 存储与性能预研(P2/远期) - -#### F1 SQLite → Postgres 迁移预研(不实施) - -**功能描述**:README 已知约束第 3 条:SQLite 单写者(SetMaxOpenConns(1)),并发上来后迁 Postgres,E10 预留多 schema。本 Story 只做预研报告:压测当前单写者瓶颈水位、gorm/database-sql 切换成本评估、多 schema 演进路径细化——**不动代码**。 - -**验收标准**:产出 `docs/postgres-migration-study.md`(QPS 水位、迁移步骤、风险清单),评审通过 - -#### F2 管理员鉴权缓存(视 A1 上线后数据决定) - -**功能描述**:README 已知约束第 6 条:每次请求回查 Casdoor userinfo(5s 超时 fail-closed),量大时加短期缓存。A1 全覆盖后请求量放大 N 倍,视延迟数据决定是否做 30-60s 短缓存(失效语义:登出/踢人延迟窗口 ≤ 缓存 TTL,需业务接受)。 - -**验收标准**:Given 缓存开启,When 同 token 60s 内 100 请求,Then Casdoor userinfo 调用 1 次;登出后 ≤TTL 内失效 - -**待澄清问题**:缓存 TTL 与安全容忍度的权衡需业务方签字 - -### 未列入本版的 Stories(备 Phase 2 引用) - -- E8 用户偏好(需求未定义,融合方案 P3 项) -- 流式 SSE 对话(D2 同步版先行) -- 国际短信 / countryCode(C2 对齐后视需求) -- E10 同实例多 schema 实施(F1 预研后另立 PRD) -- 网关级限流与 IP 风控 -- AI 用量成本核算(token 单价配置)与账单 -- llm 对话历史持久化(多轮对话 session) -- 旧 apes-Authon-Server/Web 的处置(归档 or 删除,需组织决策) - ---- - -## 7. Constraints & Risks - -### 技术约束 - -- **四条架构铁律(README 第 1 节)不可违背**:Casdoor 永不修改(submodule 锁 tag);扩展层只走 REST/Webhook/数据/前端四重契约;apes_ 表零外键;可一键升级(upgrade.sh + compat-check.sh 门禁)——本 PRD 全部 Story 均在扩展层/网关/web3 自有代码内实现,零 Casdoor patch -- **鉴权事实源唯一**:扩展层不自行解 JWT/验签名,一律回查 Casdoor userinfo——禁止为性能在扩展层引入密钥(见 Anti-metrics) -- **运行时锁定**:Go 1.25 + gin + SQLite(单写者)+ Vite 5 原生 JS——不引入新框架/ORM;SQLite 的写并发约束是 B1 配额中间件的性能边界,压测先行 -- **migrate() 约定**:DDL 必须以 `;\n` 结尾(strings.Split 拆分),新表必须带 org 索引 - -### 设计约束 - -- 前端新页面(/skill、/org、/tokens、/huoban、playground)严格遵守 `{ html, mount }` 模块约定 + main.js 的 routes/navItems 双登记,无框架依赖、产物极小(1-6 KB/chunk)的风格保持 -- 后端 handler 沿用 `gin.H{"error": ...}` 错误风格 + 按 E 编号分文件 + `r.Group("/api/apes/xxx")` 按组挂载 -- 每个新能力按 README 第 14 节六步走:建表→service→handler→挂路由→前端页→验证+落文档(contracts/ 补设计稿 + README 补端点/配置表)——**文档即契约,代码与文档同步是硬要求** - -### 数据 & 隐私 - -- 涉及个人数据:手机号(登录主键)、短信验证码(A3 后为散列)、操作日志(行为审计)、LLM API Key(仅存引用名,真实密钥在 env/vault,不落库不进日志——既有设计保持) -- 合规考虑:① op_log 无保留期限策略,需法务确认;② AI 对话内容(D2 后产生)若留存需用户知情;③ 短信码散列化(A3)后,库泄露不可反推验证码——但手机号仍明文,属可接受范围(登录必需) - -### 关键风险表 - -| 风险 | 可能性 | 影响 | 缓解措施 | -| --- | --- | --- | --- | -| **关键假设错**:无产品线实际接入 | M | High | Release 1 启动前拿书面接入意向;Epic E/F 排最后可随时砍,收缩为 A+B+C+D 自用闭环 | -| **A1 中间件全覆盖后 Casdoor userinfo 成为单点放大器**(每请求回查) | High | High | fail-closed 语义保持(Casdoor 挂→502 不放行);F2 短缓存放 Release 3 视数据启用;压测先行 | -| **A1 破坏存量调用方**(若有系统依赖无鉴权直连 :8001) | M | High | 走查存量流量确认调用方清单;dev/prod 分级灰度(先 prod 关端口再上中间件);web3 零改动(auth.js 已带 Bearer) | -| **SQLite 单写者撑不住配额扣减中间件**(每次业务调用 +1 写) | M | M | B1 压测(50 并发原子扣减用例);不行则配额计数内存聚合+批量落库,或提前触发 F1 | -| **C1 短信 Provider 上线后遭遇运营商限频/审核** | M | M | 既有单号冷却/日限参数兜底;先灰度单租户;log provider 保留为降级开关 | -| **D2 invoke 的模型调用超时链路**(LLM 延迟不可控) | M | M | invoke 超时独立配置(区别于 5s 探活);504 明确语义;前端 playground 展示耗时 | -| **契约修订漂移**(C2 六处偏差逐项改码或改契) | M | L | 决策表落 contracts/ 修订记录;compat-check.sh 契约测试同步扩 | -| **已识别未修复问题**(走查发现,本 PRD 已全部纳入):30+ 端点零鉴权(A1)、compose 暴露 8001(A2)、web3 硬编码 client_secret(A3)、短信码明文(A3)、verify 脚本硬编码密码(A3)、compat-check 硬编码路径(A3/E3)、SMS Provider STUB(C1)、LLM vault 空壳(D2)、LLM_MODULE_ENABLED 无效(D2)、dept/member 越权(A1)、webhook 无签名强制/无幂等(A1/E1) | — | — | 全部映射到 Epic A/C/D/E,Release 1-3 分批清零 | - ---- - -## 8. Release Approach - -### 推荐顺序与理由 - -**先关门、再装锁、后营业**——A1/A2/A3(鉴权+网关+凭证)是「系统敢不敢见公网」的问题,其他一切能力建设在裸奔状态都没有意义;B(配额联动)让「商用账权」的核心承诺变真;C(登录生产化)打通外部用户进入的最后一公里;D(AI 运行层)是增量价值;E/F 是工程健康度,可随接入节奏弹性伸缩。 - -### Release 1 — 安全关门(约 3 周) - -- **Story 清单**:A1、A2、A3 -- **交付价值**:任何未鉴权请求进不来(中间件全覆盖 + 网关预检 + 端口收敛),仓库与前端产物零明文凭证,短信码散列化——「受保护端点覆盖率 100% / 越权 0 / 直连全 401」三项 primary 指标达成 -- **关键假设测试**:A1 上线后观察 2 周真实调用(内部 + 灰度租户),确认无存量调用方被破坏,同时催收产品线接入意向 - -### Release 2 — 商用闭环(约 4 周) - -- **Story 清单**:B1、B2、C1、C2 -- **交付价值**:配额从裸接口变为强制拦截(402 语义可信)、套餐改绑联动、真实短信通道上线、E7 契约对齐——平台第一次能对业务方承诺「接进来就能管住用量」 -- **依赖**:B1 依赖 A1 的中间件基建;C1 依赖 A3 的散列存储 - -### Release 3 — AI 运行层与工程健康(约 4 周) - -- **Story 清单**:D2、D1、D3、E1、E2、E3、F1(预研) -- **交付价值**:Agent invoke + 配额计量(AI 用量进入管控)、Skill 补齐、web3 补 3 页(org/tokens/huoban)、webhook 幂等、测试与 CI 可移植——接入方获得完整的管理面与运行面 -- **前置门槛**:至少 1 个产品线书面接入意向(关键假设验证);F2 视 A1 上线后的延迟数据决定做否 - -### Phase 2(后续,另行立项) - -多 schema 实施、SSE 流式、网关限流、AI 成本核算、E8 用户偏好、旧 Authon 仓处置——见「未列入本版的 Stories」。 - -### 上线考虑 - -- **上线前通知**:各产品线负责人(Release 1 起 /api/apes/* 全部需要 token——若有直连调用方必须迁移)、安全负责人(client_secret/admin 密码轮换窗口)、法务(op_log 保留期限) -- **Soft launch 建议**:A1 中间件先在 dev compose 验证 1 周 → prod 先做端口收敛(A2,观察是否有东西悄悄在直连)→ 再上中间件(A1)→ 最后凭证轮换(A3);C1 短信通道先单租户灰度再放量 -- **上线后头 2 周监测**:① 401/403 比例(区分攻击流量 vs 存量破坏);② Casdoor userinfo 端点 QPS 与扩展层 P99 延迟(A1 放大效应);③ SQLite 写队列等待(B1 配额扣减);④ 短信送达率与 sms_locked 触发率(C1 防爆破是否误伤);⑤ op_log 写入速率(幂等去重效果) - ---- - -> ⚠️ **薄弱章节提示(thin_sections)** -> -> 1. **Personas(中等)**:三位 persona 由仓库代码角色推断,未经真实访谈——建议对 apes-Arise 工程团队与 1-2 个产品线开发者做快速访谈校准。 -> 2. **Goals & Success Metrics(中等)**:目标值为工程估算(如「复合登录 ≥95%」),无业务基线——Release 1 启动前与接入方共同锁定。 -> 3. **Background 商业论证(薄)**:本 PRD 为工程交付文档,「多产品线为何必须接入 Authrix 而非自建/沿用单体」的商业论证未展开——向上汇报请配合 `/设计提案`。 -> 4. **待澄清项较多**:A1(存量调用方迁移窗口)、A3(PKCE vs BFF)、B2(降档策略)、C2(六处偏差决策表)、D2(日志表归属)——均已在各 Story 内标注,建议 PRD 评审会一次性决议。