完成40%

This commit is contained in:
rain
2026-07-23 17:21:27 +08:00
parent f1edc6b533
commit bb6431b319
114 changed files with 10931 additions and 877 deletions
+160 -22
View File
@@ -1,8 +1,8 @@
# 接口与页面映射总表
> 更新日期:2026-07-22
> 阶段状态:阶段 0、导航任务 1—10、A01/A04/A05 TAC 客户端、领域上下文基础与 M07 反馈客户端已完成;家谱工作区、G03 原子创建、M06 帮助、个人资料读写、通知读写、M10 服务端退出、M04 密码凭证与 M05 手机号换绑三人审查及 OpenAPI 红灯已完成;T01、TAC、工作区、G03、帮助、profile、通知、logout、password 与 phone-change 后端接口门禁当前红灯;MuMu 原生矩阵待执行
> 接口状态:已完成 A 系列认证/TAC、G 系列第一轮、G01/G05 工作区专项G03 原子创建专项、F 系列当前写接口边界、M06 帮助、M07 反馈、M01/M02/M03 个人资料读取、M02 资料写入、N01/N02/M01/G01 通知读写、M10 当前设备退出、M04/四条密码 wire、M05/全活动 OTP wire、T01 世系图及 2026-07-22 新线上 OpenAPI 差异核对;其余操作仍待逐项审查
> 阶段状态:阶段 0、导航任务 1—10、A01/A04/A05 TAC 客户端、领域上下文基础与 M07 反馈客户端已完成;家谱工作区、G03 原子创建、M06 帮助、个人资料读写、通知读写、M10 服务端退出、M04 密码凭证与 M05 手机号换绑三人审查及 OpenAPI 红灯已完成;普通加入、邀请码直入与 G11 设置三人审查及 OpenAPI 红灯也已完成;T01、TAC、工作区、G03、帮助、profile、通知、logout、password 与 phone-change 后端接口门禁当前红灯,加入、邀请与 settings 同样保持红灯MuMu 原生矩阵待执行
> 接口状态:已完成 A 系列认证/TAC、G 系列第一轮、G01/G05 工作区专项G03 原子创建、Task36 普通加入、Task37 邀请码直入与 Task38 G11 设置专项、F 系列当前写接口边界、M06 帮助、M07 反馈、M01/M02/M03 个人资料读取、M02 资料写入、N01/N02/M01/G01 通知读写、M10 当前设备退出、M04/四条密码 wire、M05/全活动 OTP wire、T01 世系图及 2026-07-22 新线上 OpenAPI 差异核对;其余操作仍待逐项审查
## 一、权威边界
@@ -23,12 +23,12 @@
### 2.1 账户、启动与登录
- APP 不设游客模式。首次打开、无有效凭证或凭证过期时进入 A01;有效登录态进入 G01。
- A01 是唯一活动登录页,承载短信登录、受后端合同阻塞的密码登录入口、注册、找回密码、协议入口和尚未接入的微信登录入口。当前默认短信登录;密码登录在后端能够强制消费 TAC 票据前保持可见但不可用。登录成功统一到 G01,不直接恢复上次浏览的深层页面
- A01 是唯一活动登录页,承载密码登录、短信登录、注册、找回密码、协议入口和尚未接入的微信登录入口。当前默认密码登录;开发/联调 wire 已按线上 `POST /genealogy/app/auth/login` 接通,成功统一保存 `AppLoginVo.access_token` 并进入 G01。默认 `runtimeConfig.mode=mock` 不会发真实请求;remote 测试入口先展示 static TAC,但线上登录接口尚未消费票据,因此只可用于联调,不能据此解除生产发布红灯
- A04 注册成功的目标是建立登录态后进入 G01。2026-07-22 新线上 OpenAPI 已确认 `POST /genealogy/app/auth/register` 与两种登录操作统一返回 `RAppLoginVo``data` 引用 `AppLoginVo`,会话字段为 `access_token``utils/api.js` 只读取这一当前字段。正式接入应保存有效会话后直接进入 G01,不让用户重复登录,也不为受保护旧快照保留并行兼容路径。行为验证与短信发送时机仍在后续短信状态机阶段核对。
- A05 重设成功后不自动登录:返回 A01,保留合规的手机号信息,切换密码方式、聚焦密码框并让用户使用新密码登录。
- A04、A05、M04 当前预览仍共用 `utils/validation.js``832+字母数字` 旧策略;Task33 已证明该规则只在客户端且无法作为生产 owner。门禁通过后必须与 A01/A04/A05/M04 的 MD5 wire 同批原子迁移为服务端权威的 15—64 Unicode code point、NFC、允许空格与 Unicode、无组成规则,并同步替换本地唯一 owner、页面文案和测试;不得只改 M04 或保留双轨。
- A01 未勾选协议时在协议区域就近高亮并显示错误,不弹原生提示、不跳页;用户勾选后立即清除错误状态。
- A01、A04、A05 已接入同一个 `TacVerification``static/tac/`,不能把本地滑动成功冒充服务端验证。A01 短信登录使用 `APP_SMS_LOGIN`A04 使用 `APP_REGISTER`A05 使用 `APP_FORGOT_PASSWORD`;三者都先从 `/captcha/require` 取得严格 provider/type 合同,再由 `/captcha/challenge``/captcha/verify` 换取 `validToken`,携票调用 `POST /genealogy/app/auth/sms/code`验证只发生在发短信前,注册或重设提交不重复验证;手机号改变会作废旧上下文,关闭、失败或离页保留表单但不得继续发送。密码登录体尚无票据字段或其他强制绑定证据,因此密码登录入口保持不可用
- A01、A04、A05 已接入同一个 `TacVerification``static/tac/`,不能把本地滑动成功冒充服务端验证。A01 短信登录使用 `APP_SMS_LOGIN`A04 使用 `APP_REGISTER`A05 使用 `APP_FORGOT_PASSWORD`;三者都先从 `/captcha/require` 取得严格 provider/type 合同,再由 `/captcha/challenge``/captcha/verify` 换取 `validToken`,携票调用 `POST /genealogy/app/auth/sms/code`A01 密码联调入口暂借现有 TAC 场景做客户端前置后调用线上密码登录 wire,但 `validToken` 未进入或被服务端消费;它不是安全闭环,`API-AUTH-TAC-001` 仍阻止 remote 配置成为生产发布配置
- A01/A04/A05 已实现可发送、发送中、60 秒倒计时、失败重试、手机号变更失效、验证码到期和重复发送保护;短信码精确为 4 位。认证 HTTP 只接受 HTTP 200 严格 envelope,统一 15 秒超时,Android 返回或页面卸载会中止 RequestTask,迟到回调不得改变已离开页面。真实限流、前后台剩余时间恢复和多设备重放仍需后端集成与 MuMu 证明。
- 凭证过期先回 A01,再显示项目自定义的单按钮信息弹窗并聚焦登录表单;主动退出清除凭证,但可以保留用户上次选择的密码或短信登录方式,不保存密码。
@@ -46,7 +46,7 @@
- G03 当前在同一页面实例内依次完成“创建家谱”和“录入始祖”,不接收 `step``genealogyId` 路由参数;门禁前本地成功无业务结果进入 G05 明确预览态。生产目标不允许创建中断成空谱:第一步零网络写,最终按钮一次原子创建家谱、OWNER 与唯一始祖;进程终止后按 operationKey 查询服务端操作状态,不恢复已删除的路由步骤合同,也不让 G01 承担半成品恢复卡。
- G05 同一路由区分公开预览与成员视图。公开预览不得闪现成员隐私或管理入口;所有者和普通成员采用最小权限模型,最终权限以接口合同为准。
- G05 首屏按身份确认、来源确认、可信度确认三层组织信息;世系是次级入口,不自动抢占首次进入流程。
- G11 只维护当前可解释的名称、访问预设和家谱简介;门禁前本地 fixture 的“仅成员可见”“公开可申请”仍由 `utils/genealogy-contracts.js` 映射旧 `visibility/joinMode` 并对未知组合失败关闭。任务 35选定后端同版迁移为 APP 读、建、改唯一 `GenealogyAccessPreset`,落地时必须删除旧 pair 与客户端数字映射,不双读;不虚构“转让管理员”等能力。G12 本地批量预览固定按完整序列处理,支持最多 500 代、单代 50 字符和接口声明的分隔符;发现 ACTIVE 世代缺口时停止保存,不能静默错位
- G11 只维护当前可解释的名称、访问预设和家谱简介;门禁前本地 fixture 的“仅成员可见”“公开可申请”仍由 `utils/genealogy-contracts.js` 映射旧 `visibility/joinMode` 并对未知组合失败关闭。任务 38固定唯一 merge PUT、fresh overview baseline、`settingsVersionIf-Matchtyped 409`、24/80 code point、活动 PENDING 迁移阻断和 unknown 对账;后端门禁通过前不删除诚实本地预览或接宽松接口,见 2.16/5.15。任务 39 已把 G12 收紧为唯一 GET/PUT、ACTIVE-only 完整集合、`poemSetVersionIf-Match`、完整候选校验、软停用与 unknown 三方对账;后端绿前仍只做本地预览,见 2.17/5.16
### 2.3 页面状态与请求结果
@@ -161,8 +161,8 @@ T01/T03—T08 当前共用 `data/mock.js` 的唯一成员夹具 owner:列表
- G06:新线上公开搜索已经返回类型化家谱对象,但仍没有显式 `canApply`、六类关系字典、邀请码校验、邀请码目标解析或直接加入端点;未来必须由服务端返回可信的 `canJoinByInvite` 或等价结果,搜索申请资格不能代替邀请码资格,也不能信任 `source=invite` 查询参数。
- G08`GenealogyJoinApplyBody` 提供 `applicantName/phone/relationDesc/applyReason/inviterUserId`,当前页面的 `realName/relation/message` 仍需显式映射、手机号来源和必填规则确认;接口没有邀请码票据字段。搜索来源本地完成仅进入 G09 预览,邀请码来源本地完成仅回 G01,二者都不建立成员关系。
- G09/G10:新线上我的申请和待审核列表已返回类型化申请行,但申请状态仍缺正式 enum/字典;真实撤回和审核必须携带服务端词法 `applyId`。线上审核体为 `{status,auditRemark}``status` 必填且匹配 `[12]`1 通过、2 拒绝),`auditRemark` 最长 500`utils/api.js` 已同步严格校验,旧 `{approved}` 路径已删除。当前本地 `LOCAL_WITHDRAWN` 和审核预览仍不得当成服务端状态。
- G11:受保护旧双导出的 `GenealogyUpdateBody` 与线上 `AppGenealogyUpdateBody` 都有 `genealogyName/intro/visibility/joinMode`页面已删除不存在的 `accessNote``utils/genealogy-contracts.js` 当前只为 fixture 暂时映射 `2/0``1/1`;任务 35 已把远端 owner 收紧为闭合 `AppGenealogySettingsUpdateBody` `genealogyName/intro/accessPreset`,并要求 `AppGenealogyVo` 同用枚举。新合同落地时原子删除旧 DTOpair、映射和对应旧测试,不保留邀请码 mode 的暗中兼容,见 5.12
- G12旧双导出的批量体为 `{poemText,disableMissing}`;输入总长上限 26000 字符、单代最多 50 字符、单批最多 500 代,停用遗漏后续记录时不得删除历史。新线上 `GenerationPoemBatchBody` 还暴露 `genealogyId`,说明为“由路径参数写入,客户端不得自行指定”,并丢失了旧导出中的单代/单批说明,必须由后端同版本导出确认哪组约束仍有效。客户端解析、状态 `0/1`、词法 `poemId` 保留、重复世代失败关闭、完整 ACTIVE 序列检查和 50 条分批渲染已由共享 owner 与 Node 冒烟锁定;页面运行时合同还覆盖 `disableMissing` 保留/停用/取消恢复及 500×50 个补充平面字符的最大合法包络。旧导出文字列“空格”等分隔符但示例含换行,当前解析器把 Unicode whitespace 视作分隔符;Tab/NBSP 是否属于正式合同仍须后端澄清。普通列表只应给可查看者返回正常状态,维护列表只给内容编辑者返回正常与停用记录。接口仍没有 `startGeneration`,且未明确批次首项对应哪一世;真实接入必须以服务端 preview items 为差异真相,在后端澄清批次起点前不得发送本地合并结果
- G11:受保护旧双导出的 `GenealogyUpdateBody` 与线上 `AppGenealogyUpdateBody` 都有 `genealogyName/intro/visibility/joinMode`且缺版本、If-Match、closed merge 与 typed 错误。任务 38 保留现有 PUT 为唯一 dirty-only owner,删除 generic GET/PATCH,复用 overview 的 `RAppGenealogyVo`,新增 `GenealogyName/GenealogyIntro/GenealogySettingsVersion` 与 409 三分支;G03 门禁只继续拥有共享 accessPreset 和旧 DTO/pair 删除,见 2.16/5.15
- G12任务 39 已否决旧 `{poemText,disableMissing}`、collection POST、逐行 PUT、batch preview/save 与 management 多 owner,固定唯一 GET/PUT aggregate。请求改为显式 0—500 行 `{poemId?,generationNo,generationText}`disableMissinggenerationNo 唯一排序且声明严格升序;新行省略 ID,已有 ACTIVE ID 可显式移动,重复文字允许。false 保留遗漏 baseline、true 软停用,empty false no-op、empty true 清空 ACTIVE;服务端先校验声明严格升序,再构造完整 merged candidate,随后验证 ID/世代/slot/连续/最终容量并原子写,历史不物理删除。Unicode 精确为 NFC、1—50 code point 且拒绝控制/bidi/zero-width/孤代理。后端门禁仍有 72 项红灯,真实接入不得再依赖服务端 preview items 或猜批次首代,见 2.17/5.16
### 2.9 新线上 OpenAPI 差异与运行边界
@@ -177,7 +177,7 @@ T01/T03—T08 当前共用 `data/mock.js` 的唯一成员夹具 owner:列表
认证客户端批次已经完成,但 `runtimeConfig.mode` 继续固定为 `mock`;下列问题关闭、后端提供同版本双导出并完成真实联调前,客户端不得切换远端或宣称登录注册可上线:
- `API-AUTH-TAC-001`:密码登录体尚无票据字段。后端必须让 `POST /genealogy/app/auth/login` 强制消费与短信发送相同安全语义、绑定 `APP_PASSWORD_LOGIN + tenant + client + canonical phone` 的短时单次票据;在此之前 A01 密码登录入口保持不可用,不能仅由客户端先展示滑块
- `API-AUTH-TAC-001`:密码登录体尚无票据字段。后端必须让 `POST /genealogy/app/auth/login` 强制消费与短信发送相同安全语义、绑定 `APP_PASSWORD_LOGIN + tenant + client + canonical phone` 的短时单次票据。A01 已开放开发/联调 wire,但客户端先展示滑块不能充当服务端强制校验,故默认仍为 mock 且生产发布保持阻塞
- `API-AUTH-TAC-002`2026-07-22 对真实 `APP_REGISTER` 请求只读联调时,`POST /captcha/challenge` 返回 HTTP 500 且响应体为空。后端必须修复并提供成功、无效场景、过期、限流和服务不可用的稳定错误 envelope;不得用客户端重试掩盖空 500。
- `API-AUTH-TAC-003`:当前 `VerificationCheckBody` 未将 `providerCode/captchaType/payload` 全部声明为必填,根对象与 TianAi/SystemImage payload 也未关闭额外字段,且缺少以 evidence/provider 为 discriminator 的 `oneOf`。后端必须建立关闭额外字段的严格分支;客户端提交的 provider/type 不能替代 challenge 的服务端所有权。`tests/auth-tac-openapi-contract.ps1` 当前聚合六项结构缺口并输出 `AUTH-TAC-OPENAPI-CONTRACT BLOCKED`
- `API-AUTH-TAC-004`:验证码中心必须是唯一 owner。`/captcha/require` 返回服务端绑定 `tenant/client/scene/canonical subject/riskPolicyVersion``verificationSessionId`、允许方法和短时有效期;`required=false` 也必须直接返回可供短信端点原子消费的一次性 `validToken`。同一 session 只允许一个活动 challenge,刷新或切换方法立即作废旧题但不清零失败计数;`/captcha/verify` 只在服务端验证 evidence 并结合本地风险策略通过后签发票据,票据继续绑定 method/assurance/audience。`/sms/code` 必须在同一事务中完成 `ISSUED → CONSUMED` 与唯一短信 outbox 创建:相同幂等键返回原结果,并发或不同键重放不能创建第二个任务;跨手机号、场景、租户或客户端全部失败。
@@ -215,29 +215,77 @@ T01/T03—T08 当前共用 `data/mock.js` 的唯一成员夹具 owner:列表
- 个人资料:Task28 固定 `GET /genealogy/app/auth/profile` 为 M01/M02/M03 唯一读取 owner。首批 wire 只 required canonical `phone``nickName/realName/email` 未设置时省略,出现时分别满足 1—30、1—30、email 且 1—100。adapter 立即掩码手机号并丢弃 `userId/avatar/status` 等未消费字段,当前 `PROFILE-OPENAPI-CONTRACT BLOCKED`,见 5.5。Task31 保留 PUT 为唯一 dirty-only merge owner,只允许脏的三项资料;省略保持,realName/email 精确空串清空,`profileVersionIf-Match409` 防并发覆盖,当前 `PROFILE-UPDATE-OPENAPI-CONTRACT BLOCKED`,见 5.8。头像与 M05 换绑继续各自独立。
- 密码与手机:Task33 已判定 `PUT /genealogy/app/auth/password` 和登录/注册/找回共用的 32 位十六进制 MD5 不可上线;四条入口须原子迁移到 raw writeOnly、15—64 Unicode/NFC、blocklist/限速/慢哈希。M04 200 前撤销包括当前设备在内的 ALL access/refresh sessionunknown 也清本机回 A01,崩溃窗口由无秘密的 sessionEpoch marker 关闭,当前 `PASSWORD-CHANGE-OPENAPI-CONTRACT BLOCKED`,见 5.10。Task34 又固定 M05 为 currentPassword 再认证+新号 `APP_PHONE_CHANGE` TAC/6 位 OTP;换绑发码必须走专用 SaToken operation,最终 200 前换号、消费 OTP、提升 epoch、撤销 ALL session并持久化旧号通知,当前 `PHONE-CHANGE-OPENAPI-CONTRACT BLOCKED`,见 5.11。M04/M05 都不伪提交。
- 家谱创建:Task35 否决空谱+通用人物两写,固定 G03 最终按钮一次 atomic bootstrap;访问规则同版统一为 accessPreset,地区只提交 selectable 项的词法 code,结果未知按 operationKey 精确查询且本地不存始祖 PII。当前 `G03-BOOTSTRAP-OPENAPI-CONTRACT BLOCKED`,见 5.12;通过前不接宽松 create API。
- 家谱设置:Task38 保留唯一 PUT 但收紧为原子 dirty-only merge`/overview` 是 fresh baseline 唯一读取 owner`GenealogySettingsVersionIf-Match409` 与 T01/M02 并发模型一致,PUBLIC_APPLY 关闭时与普通申请准入串行化。当前 `G11-SETTINGS-OPENAPI-CONTRACT BLOCKED`,见 5.15;后端绿只允许开始客户端 TDD,不允许直接接页。
- 字辈集合:Task39 固定唯一 `appGetGenerationPoemSet/appUpdateGenerationPoemSet``GenerationPoemSetVersionIf-Match`、完整候选与 `FRESH_GET_THREE_WAY_NO_AUTO_PUT`;浏览器预检由 `APP_GATEWAY_PREFLIGHT` 唯一声明。当前 `G12-GENERATION-POEM-OPENAPI-CONTRACT BLOCKED`,见 5.16;页面保持本地预览。
- 帮助与反馈:M06 首批固定 `GET /genealogy/app/help-articles` 为完整列表唯一 owner,不调用详情、不消费 `helpId`;adapter 只允许投影分类、标题和纯文本正文,分类由当前列表动态派生。受保护双导出仍是通用 `ListResult/RList`,线上专用模型又缺 required、正文格式、仅发布和顺序语义,匿名行为也与文档 401 冲突,因此 `HELP-CENTER-OPENAPI-CONTRACT BLOCKED` 保持红灯,详见 5.4。`AppFeedbackBody.feedbackContent` 必填,`feedbackType/contactInfo` 可选且无 enumM07 已由 `appApi.submitFeedback` 精确 POST `/genealogy/app/feedback`,调用方不能关闭认证头,只接受 HTTP 200 与整数成功 `code`。mock 模式不伪提交;成功、确定失败、结果未知和迟到输入已分离。两页真实服务与 MuMu 验收都等待认证远端门禁关闭。
- 邀请:`GET /genealogy/app/promotions` 只返回推广内容与通用 `targetUrl`,没有家谱邀请码签发、校验、失效或直接加入端点,不能支撑产品邀请闭环。M08 已删除硬编码码值、剪贴板和海报伪能力,并显示不可用;未来只能接入“校验成功直接加入且不生成审核记录”的单一路径。
- VIP 与订单:线上存在套餐和订单的查询/创建端点,但当前文档未闭合支付方式、价格精度、订单状态、重复下单、支付回调、退款与续费语义。M09 不读取查询参数、不生成演示订单并保持不可用,待独立支付合规审查后再开放。
- 退出:Task32 固定 `DELETE /genealogy/app/auth/logout` 只撤销当前 bearer credential family,其他设备保持有效;同 client 的 active/revoked/expired 凭证重复调用都收敛为同一 200,非法/client 不匹配为 typed 401。客户端未来由唯一 logoutCoordinator 在同一同步段捕获 A、清本地并 bump epoch、用显式 A 启动不随 M10 卸载取消的请求,然后立即进入 A01;迟到结果不再 clear。当前本地/线上合同都缺 required、范围/幂等/no-store 与复用反例,`LOGOUT-OPENAPI-CONTRACT BLOCKED`,见 5.9。
### 2.14 任务 36 普通加入申请合同边界
普通申请唯一流程为 G06 鉴权搜索 → G08 普通申请 → G09 mine/撤回,以及 G10 pending/审核;邀请码成功后直接加入且不生成审核记录,另立后续批次。七个稳定 owner 为 `appSearchPublicGenealogies``appCreateGenealogyJoinApplication``appGetGenealogyJoinApplicationRequest``appListMyGenealogyJoinApplications``appWithdrawGenealogyJoinApplication``appListPendingGenealogyJoinApplications``appReviewGenealogyJoinApplication`。所有 genealogyId/applyId 使用词法字符串,三个列表使用绑定 actor/filter 的稳定 cursor、limit 1—50、无 total。
搜索投影只保留识别家谱所需字段与单一 viewerStatemine 以 PENDING/APPROVED/REJECTED/WITHDRAWN discriminator 表达,只有拒绝分支必带申请人可见 rejectionReasonpending 精确为 `applyId/applicantName/relationDesc/applyReason?/submittedAt`,递归禁止 phone、user、inviter、auditor。申请体只含 `applicantName/relationDesc/applyReason?`,审核只含 APPROVE 或 REJECTrejectionReason;统一 `JOIN_APPLICATION_TEXT_V1` 规范 NFC、边界空白和换行。
Idempotency-Key/requestKey 共用 `GenealogyJoinApplicationRequestKey``gja.{13 位毫秒时间}.{22—43 位 base64url CSPRNG}`,600 秒接受窗、300 秒未来偏差、120 秒 PENDING 收敛;canonical scope 包含 method/path/genealogyId/tenant/account/client/body。状态 GET 纯读且只返回 PENDING/SUCCEEDED/FAILED_NO_COMMIT。撤回和审核都用 `WHERE status=PENDING` CAS;批准同事务创建唯一成员并写终态。静态 OpenAPI 只能锁定合同,真实唯一约束、零写、fencing、事务和 HTTP/CORS 仍须部署集成反例。
当前 `tests/join-application-openapi-contract.ps1` 精确输出 `JOIN-APPLICATION-OPENAPI-CONTRACT BLOCKED`;旧 G-series/core-flow 不再拥有 APP 加入 schema。后端门禁前 G06/G08/G09/G10 不接远端。后续接线必须删除 G10 手机号、三处消息中心承诺、LOCAL_WITHDRAWN 和遗留 adapter,并补状态机、无障碍与 MuMu 矩阵。
### 2.15 任务 37 邀请码签发与直接加入合同边界
邀请与普通申请是两个独立 owner。六个稳定 operation 为活动票据列表、无 body 幂等签发、撤销、JSON body 安全解析(无票据消费/成员写,仅轮换短时 grant 摘要)、无 body 直接兑换和兑换状态查询。真实流程固定为 M08 管理票据,G06 输入原码→resolve 最小可信目标与短时 redemption token→明确确认“直接加入、不经审核”→redeem→status 收敛→刷新 `/mine`→安装 context→G01/G05;不再进入 G08,不提交姓名/关系/理由,不创建申请、审核记录、人物或亲属关系。
首版票据固定单次、24 小时和至少 128 位随机量;原码只允许在 resolve body 与签发成功/同 key 600 秒恢复窗中出现,查找使用 HMAC,短时恢复使用隔离 KMS 密文。列表、撤销、兑换、status、错误、URL/query、导航、storage、日志/APM/分析均不得出现原码或 redemption token。签发 unknown 使用 `{sessionEpoch,genealogyId,requestKey,startedAt}` 重放原路径/key,不自动换 key;600 秒只限制不存在 key 的首次认领,已存在 key 在窗内返同码、窗后返同 ticket metadata+秘密不可恢复,不签第二码。resolve 可轮换一个 HMAC 摘要 grant但不消费票据,token 最多五分钟并绑定 tenant/account/client/ticket/version/genealogy/authorizationEpoch。
兑换使用敏感 header 与 `gir` key,在一个事务中重验票据、grant、权限、READY、功能、账号资格与容量,完成 ACTIVE→CONSUMED CAS、唯一 MEMBER 和 SUCCEEDED receiptFAILED_NO_COMMIT 保证票据未消费且成员未建。已有普通 PENDING 返回 `ACTIVE_PENDING_APPLICATION`,用户先去 G09 撤回,邀请流程不得暗改任务 36。当前 `tests/invite-ticket-openapi-contract.ps1` 精确输出 `INVITE-TICKET-OPENAPI-CONTRACT BLOCKED`;后端门禁前 M08 保持不可用、G06/G08 保持诚实预览,不接虚构接口。
### 2.16 任务 38 G11 家谱设置版本化写入合同边界
G11 全局唯一写 owner 保留 `PUT /genealogy/app/genealogies/{genealogyId}`operationId 为全局唯一 `appUpdateGenealogySettings`;不新增 PATCH、`/settings` 旁路或递归引用设置 body/字段的第二写入口,并删除重复 generic GET。G11 只消费 workspace owner 门禁结论,每次进入 fresh 调用 `/{genealogyId}/overview` 建立 canonical baseline,不能信任路由、`/mine` 卡片、旧 G05 内存、roleType 或 fixture。overview 与 PUT 200 共用精确 `{code,data}``RAppGenealogyVo`,不建立第二套 settings snapshot。共享 `GenealogyName` 统一创建/更新/读取为非 null、NFC、无边界空白、无换行或控制字符的 1—24 Unicode code point`GenealogyIntro` 为非 null 精确空串或规范 1—80,正文只允许内部 LF 并拒绝 CR/tab/其他控制字符,空串清空、省略保持;accessPreset 仍只引用 `GenealogyAccessPreset`
`AppGenealogySettingsUpdateBody` 关闭额外字段并只允许 1—3 个 `genealogyName/intro/accessPreset` 脏属性,服务端事务内重验管理权限并原子 mergecanonical no-op 返回当前 200、版本不升且无领域副作用。并发唯一使用 `AppGenealogyVo.settingsVersion` 和 required `If-Match`body 不重复版本;版本以 `x-version-scope-fields` 锁定只随三个 canonical 设置实际变化。旧版本返回 typed 409 `GENEALOGY_SETTINGS_VERSION_CHANGED`current `AppGenealogyVo`,不用 ETag/412409 wrapper 只允许 discriminator+三个 exact local ref 分支。另两条 409 为 `GENEALOGY_NOT_READY` 与只在实际 `PUBLIC_APPLY→MEMBER_ONLY` 且有活动待审时返回的 `ACTIVE_PENDING_APPLICATIONS`;后者引导 G10、不自动处理申请,设置 PUT 与普通申请 POST 共同声明 `ATOMIC_SINGLE_WINNER` 并在事务内重验准入。
unknown 不新增 status owner,也不持久含谱名/简介的 body。客户端冻结 baseline/dirty/version/sessionEpoch,网络、超时、408、取消、5xx 或畸形 2xx 后 fresh 读 overview 对账,不自动 PUT;目标值一致只说明当前事实,不能证明本次请求成功。成功后原子回填 canonical 模型,失效 mine/overview/公开搜索/预览缓存并重验 context。响应与 parameter/response/header/schema 解析只接受 exact local component refCache-Control 由单值 enum 固定。当前 `tests/g11-settings-openapi-contract.ps1` 稳定输出 `G11-SETTINGS-OPENAPI-CONTRACT BLOCKED`、48 项;本批不建占位 client gate,后端绿后的第一项写操作必须是实际执行生产 normalizer/coordinator 的纯 Node 失败测试。
### 2.17 任务 39 G12 字辈集合版本化保存合同边界
G12 唯一 aggregate owner 为 `GET/PUT /genealogy/app/genealogies/{genealogyId}/generation-poems`operationId 为 `appGetGenerationPoemSet/appUpdateGenerationPoemSet`;旧 collection POST、逐行 PUT、batch preview/save、management 及其 schema/response 同版删除,不引入 preview token、服务端 preview/status。两条 200 共用 ACTIVE-only `GenerationPoemSetSnapshot {genealogyId,poemSetVersion,items}`,按 generationNo 严格升序;GET 重验 canViewPUT 事务内重验 tenant、canEditContent、READY 和版本。
`AppGenerationPoemSetUpdateBody` 精确 required `{items,disableMissing}`。声明行最多 500 且严格升序;已有行 poemId 必须来自 baseline ACTIVE,新行省略 ID,稳定 ID 可随显式 generationNo 移动。false 保留未声明 baseline ACTIVEtrue 软停用遗漏项;空 false no-op,空 true 停用全部。服务端先执行 `VALIDATE_DECLARED_STRICT_ORDER`,再构造声明目标与 merged candidate,随后执行 ID/世代唯一、`GENERATION_SLOT_CONFLICT`、连续性、`VALIDATE_FINAL_ACTIVE_CAPACITY``ALLOCATE_UNIQUE_NEW_IDS` 及新 ID 非空/唯一/非 baseline 校验;swap 显式声明所有受影响行并原子成功或零写,历史不物理删除。generationText 可重复,但每项必须 NFC、1—50 Unicode code point、well-formed 且拒绝边界空白、控制/bidi/zero-width/孤代理。
唯一并发 owner 为 `GenerationPoemSetVersionIf-Match`,body 不复制版本,不用 ETag/412no-op 保持版本,ACTIVE 语义变化生成永不复用的新版本。unknown 以 baseline/version、declaredItems 和 disableMissing 构造 effective target,按 `FRESH_GET_THREE_WAY_NO_AUTO_PUT` fresh GETexisting 比较 IDgenerationNFC 文本,NEW 比较 generation+文本并要求返回 ID 新鲜;同基数且无额外 ACTIVE 行才相等。先执行 `CURRENT_EQUALS_TARGET_FIRST_NO_ATTRIBUTION`,只陈述当前事实;再判 old,否则 divergent,全部不自动 PUT。
响应只允许 typed JSON、private/no-store429 带 Retry-AfterSaToken security 必须是 JSON 数组,clientid 非空。顶层 `x-app-gateway-policies.APP_GATEWAY_PREFLIGHT` 是唯一 CORS 声明 owner,精确包含 Authorization/Content-Type/If-Match/clientid、GET/PUT/OPTIONS、显式 origin allowlist 与 600 秒;发布仍须真实 OPTIONS/preflight。当前主门禁输出 `G12-GENERATION-POEM-OPENAPI-CONTRACT BLOCKED``Issues: 72`zero issues seed 与严格大小写/ref/security/annotation 反例输出 `G12-GENERATION-POEM-OPENAPI-ADVERSARIAL-CONTRACT PASS`Unicode 输出 `G12-GENERATION-POEM-UNICODE-RUNTIME-SMOKE PASS`。后端绿前 G12 保持本地预览;绿后第一项客户端写操作必须是可执行生产 normalizer/coordinator 失败测试,再原子删除 fixture/timer/本地成功和旧合同。
### 2.18 任务 40 F01/F03 家族动态读取合同边界
本批只拥有 F01 动态列表、F03 动态详情和正常一级评论读取。唯一 owner 为 `GET /genealogy/app/genealogies/{genealogyId}/feeds``GET /genealogy/app/genealogies/{genealogyId}/feeds/{feedId}``GET /genealogy/app/genealogies/{genealogyId}/feeds/{feedId}/comments`operationId 为 `appListFamilyFeeds/appGetFamilyFeed/appListFamilyFeedRootComments`;删除两个 `/page` GET。发布、点赞、评论/回复写、回复读与媒体文件读分别留给后续独立批次。受保护双导出和线上 3.1.0 当前仍有双 owner、int64、通用/开放 response;线上评论 DTO 还含手机号、业务用户 ID 和审核字段,故不得接页。
`AppFamilyFeedReadItem` closed required `{feedId,feedContent,authorDisplayName,publishedAt,hasMedia}``AppFamilyFeedRootCommentReadItem` closed required `{commentId,commentContent,authorDisplayName,publishedAt}`。ID 均为 1—128 位词法字符串;正文非 null 且有界;publishedAt 为服务端生成的不可变 RFC3339;作者只返回授权展示名和非空退化文案。评论只含正常可见一级评论,不含 reply/删除占位/parent/level/status/审核字段。标题、标签、点赞和总评论数不是本批合同。`hasMedia=true` 时客户端在媒体读取合同前展示诚实占位,不泄露或解析 mediaOssIds,不把附件静默丢失。
两个列表使用默认 20、范围 1—50 的 opaque cursor,无 total/pageNum。feed 排序为 `publishedAt:DESC,feedId:DESC_ORDINAL`comment 为 `publishedAt:ASC,commentId:ASC_ORDINAL``UPPER_BOUND_KEYSET_LATEST_VISIBLE` 只固定上界并排除刷新前的新头部内容,后续删除/隐藏项省略、可见编辑返回该页读取时的最新内容,不宣称 MVCC snapshot。cursor 绑定 actor/session/client、谱/动态、projection/order/limit/windowUpperBound/lastTuple;每页重验 tenant、membership、feed 归属与可见性,篡改/过期 typed 400,错谱、跨主体、撤权、删除或隐藏统一 `404 FAMILY_FEED_NOT_AVAILABLE`
三个 GET 精确 200/400/401/404/429/500 typed JSON,全部 private/no-store429 带 Retry-Afterrequired SaToken/clientid 并复用 `APP_GATEWAY_PREFLIGHT`。当前 `tests/family-feed-read-openapi-contract.ps1` 输出 `FAMILY-FEED-READ-OPENAPI-CONTRACT BLOCKED``Issues: 85`zero issues 合法种子和 58 个 ref/大小写/PII/int64/offset/HTTP/cache 变异由 `tests/family-feed-read-openapi-adversarial-contract.ps1` 证明为 `FAMILY-FEED-READ-OPENAPI-ADVERSARIAL-CONTRACT PASS MUTANTS=58`。后端绿前保持复合身份 fixture,禁止连接 dormant 宽 `getFeeds` 或失败回退 fixture。后端同版本 JSON/YAML/live 通过后,客户端首写先建立直接执行生产 read normalizer/coordinator 的失败测试,再原子迁移 F01/F03 和无障碍/异常状态。
## 三、52 个活动页面映射
表中“返回或完成目标”描述业务意图,不表示现有导航 API 已经正确;导航栈阶段需要用源码扫描、测试和 MuMu 完整流程逐项验证。A04、G 系列第一轮、F 系列当前写边界、T01 和新线上差异已形成专项证据;其余接口列继续标记“待对应业务阶段 OpenAPI 审查”,避免把旧思维导图、页面 mock、旧离线快照或 PC 接口误当成当前 App 合同。
| 编号 | 页面 | 路由 | 当前业务目标 | 主要进入方式 | 返回或完成目标 | 必测状态 | 接口业务域 | 当前接口核对状态 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| A01 | 登录 | `pages/auth/a01-entry` | 完成短信认证并处理协议;密码与微信待合同关闭 | APP 启动、凭证失效、主动退出 | 成功进入 G01;取消或失败留在本页 | 短信、协议错误、TAC、发送中、倒计时、请求取消、登录失败、凭证过期 | 认证与账户 | `APP_SMS_LOGIN` 客户端链已落地;密码登录因 `API-AUTH-TAC-001` 硬关闭,后端与 MuMu 门禁见 2.10 |
| A01 | 登录 | `pages/auth/a01-entry` | 完成密码/短信认证并处理协议;微信待合同关闭 | APP 启动、凭证失效、主动退出 | 成功进入 G01;取消或失败留在本页 | 密码、短信、协议错误、TAC、发送中、倒计时、请求取消、登录失败、凭证过期 | 认证与账户 | 密码登录线上 wire 与 `AppLoginVo.access_token` 已接入,默认 mock`validToken` 未由登录接口消费,`API-AUTH-TAC-001` 仍阻止生产发布,见 2.10 |
| A04 | 注册账号 | `pages/auth/a04-register` | 建立新账号并确认协议 | A01 注册入口 | 成功建立登录态并进入 G01;取消返回 A01 | 本地校验、TAC、短信、注册中、手机号占用、成功、失败、取消 | 认证与账户 | `APP_REGISTER`、4 位码、`RAppLoginVo → AppLoginVo.access_token` 客户端链已落地;真实 challenge 与 OpenAPI 仍红灯,见 2.10 |
| A05 | 重设密码 | `pages/auth/a05-reset-password` | 验证手机号并设置新密码 | A01 忘记密码 | 成功返回 A01 的密码登录态;取消返回 A01 | 4 位码、TAC、密码策略、不一致、提交中、成功、失败、取消 | 认证与账户 | `APP_FORGOT_PASSWORD` 客户端链已落地;真实后端、Android 可访问替代与 MuMu 仍红灯,见 2.10 |
| G01 | 我的家谱 | `pages/genealogy/g01-my-genealogies` | 选择全局家谱并完成加入或创建分流 | 登录成功、根 Tab、业务完成回流 | 进入 G03、G05、G06、G09、G10、G12、T01 或 N01 | 正常、空、加载、失败、审核中、被拒绝、退出或移除、切换弹层、未读消息 | 家谱、成员关系与通知计数 | Task26 工作区红灯已建立;`/mine` 尚未接远端,见 2.8/5.3Task29 unread-count 红灯要求与 M01 共用唯一未读数 owner,见 2.13/5.6 |
| G03 | 创建家谱 | `pages/genealogy/g03-create-genealogy` | 在同页收集家谱与始祖并最终原子创建 | G01 创建入口 | 门禁前进入 G05 本地预览;生产成功按 receipt→mine cache→context→G05 唯一次序收口,放弃零写 | 地区加载/失败、重复建议、始祖校验、提交中、PENDING、结果未知、fatal/quarantined、已提交待进入、放弃确认 | 家谱与成员关系 | Task35 已建立 atomic bootstrap、无 PII operation-status、统一 accessPreset、APP 可信地区与词法 ID 的后端/客户端双红灯;开放还依赖 Task26 workspace 与 MuMu,见 2.8/5.12 |
| G05 | 家谱总览 | `pages/genealogy/g05-genealogy-overview` | 按公开预览、成员、所有者或本地预览浏览身份、来源和可信度 | G01、G06、G09、G03 本地预览 | 返回实际来源;按权限进入 T01、G08、G10、G11、G12 或 F01 | 公开预览、成员视图、所有者、本地预览、加载、空、失败、无权限 | 家谱与权限 | Task26 选定 `/overview` 为唯一 owner;最小 schema、对象级授权和错误语义未关闭,丰富首屏字段另待取舍,见 2.8/5.3 |
| G06 | 加入家谱 | `pages/genealogy/g06-search-genealogies` | 通过搜索或邀请码本地校验定位目标家谱或支系 | G01 空态或添加家谱弹层 | 进入 G05G08G09已加入或我创建时回 G01 | 初始、搜索中、结果、无结果、邀请码无效或过期、失败、六种用户关系 | 家谱搜索与邀请 | 第一轮已核对;邀请码验证与直入端点缺失,见 2.8 |
| G08 | 关系确认与入谱 | `pages/genealogy/g08-join-application` | 校验真实姓名、关系和说明并预览两种加入流程 | G06、G05 或 G09 的共享资格入口 | 搜索来源本地完成进入 G09;邀请码来源本地完成回 G01;均不建立成员关系 | 双来源、不可申请、字段校验、提交中、本地成功、失败、重复提交、放弃填写 | 加入申请与邀请 | 第一轮已核对;申请字段适配与邀请码票据缺失,见 2.8 |
| G09 | 我的申请 | `pages/genealogy/g09-my-applications` | 查看申请并预览撤回或重新申请 | G01、G08 本地流程、G06 审核中 | 已通过进入 G05;可重申记录进入 G08;本地撤回不改变服务器状态 | 列表、空、失败、待审、通过、拒绝、本地撤回、重新提交 | 加入申请 | 第一轮已核对;类型化申请行已存在,正式状态字典与词法 `applyId` 适配待补,见 2.8 |
| G10 | 入谱审核 | `pages/genealogy/g10-application-review` | 所有者预览通过或拒绝申请 | G01、G05 或 N01 审核消息 | 本页只更新本地预览;取消或返回不产生导航结果 | 列表、空、失败、通过确认、拒绝原因、提交中、无权限;拒绝字段 `aria-describedby` 保留错误关联失败聚焦 | 加入审核与权限 | 第一轮已核对;类型化申请行与审核体已存在,正式状态字典和词法 int64 适配待补,见 2.8 |
| G11 | 家谱设置 | `pages/genealogy/g11-genealogy-settings` | 本地维护名称、访问预设和家谱简介 | G05 所有者管理入口 | 保存只更新本页预览;取消恢复原值并返回 | 加载、字段校验、本地成功、失败、无权限、未保存返回 | 家谱设置与权限 | 第一轮已核对;Task35 只统一 accessPreset 字段,真实写入仍须独立 If-Match、版本/CAS、权限刷新与结果未知门禁,见 2.8/5.12 |
| G12 | 字辈诗 | `pages/genealogy/g12-generation-poems` | 分批浏览并本地维护完整字辈序列 | G01 快捷入口或 G05 | 保存只更新本地列表;取消恢复编辑快照并返回来源 | 列表、空、编辑、校验、无权限、500 代分批渲染、停用但保留历史 | 字辈与权限 | 第一轮已核对;batch 首项世代未定义,真实保存须以服务端 preview 为准,见 2.8 |
| G06 | 加入家谱 | `pages/genealogy/g06-search-genealogies` | 普通搜索,或解析邀请码后明确确认并直接加入 | G01 空态或添加家谱弹层 | 普通搜索按 viewerState 进入 G05/G08/G09邀请成功刷新 workspace/context 后进入 G01/G05 | 搜索六关系;邀请码输入、格式错、解析、目标、确认、PENDING/unknown、已加入、普通申请冲突、失效、限流 | 普通搜索与独立邀请 | Task36 搜索见 2.14/5.13Task37 六 operation 邀请红灯见 2.15/5.14,门禁前不接远端 |
| G08 | 关系确认与入谱 | `pages/genealogy/g08-join-application` | 只提交普通加入申请;真实接线时删除全部 invite source | G06、G05 或 G09 的普通申请入口 | 普通申请成功刷新 G09 | 不可申请、字段校验、PENDING、unknown、成功、失败、放弃 | 普通加入申请 | Task36 后端红灯见 2.14/5.13Task37 明确邀请不得进入 G08,现有 invite 预览待接线时原子删除 |
| G09 | 我的申请 | `pages/genealogy/g09-my-applications` | 查看四状态申请并撤回或重新申请 | G01、G08、G06 审核中 | 已通过进入 G05;拒绝/撤回可重新申请;unknown 刷新 mine | 列表、空、失败、待审、通过、拒绝、撤回、重新提交 | 加入申请 | mine 四状态、拒绝原因和撤回 CAS 由 Task36 门禁拥有;消息中心承诺待接线时删除 |
| G10 | 入谱审核 | `pages/genealogy/g10-application-review` | 审核不含手机号的 pending 最小投影 | G01 或 G05;首版不承诺通知目标 | 审核后刷新 pending;行消失只称状态变化 | 列表、空、失败、通过确认、拒绝原因、提交中、无权限、竞态冲突;拒绝字段保留 `aria-describedby`错误关联失败聚焦 | 加入审核与权限 | Task36 固定 APPROVE/REJECT、PENDING CAS 与事务权限重验;手机号节点待接线时原子删除 |
| G11 | 家谱设置 | `pages/genealogy/g11-genealogy-settings` | 门禁前本地维护;生产目标原子更新名称、访问预设和简介 | G05 管理入口,进入后 fresh overview 鉴权 | 门禁前只更新本页预览;生产成功 canonical 回填、刷新缓存/context 后回 G05 或撤权回 G01 | loading、dirty、saving、validation、saved、unknown 对账、三类 409、无权限、认证失效、不可用、未保存返回 | 家谱设置与权限 | Task38 已建立唯一 merge PUT、settingsVersion/If-Match、409 three-way、待审串行化与 unknown 红灯;页面尚未接远端,见 2.16/5.15 |
| G12 | 字辈诗 | `pages/genealogy/g12-generation-poems` | 分批浏览并本地维护完整字辈序列 | G01 快捷入口或 G05 | 门禁前保存只更新本地列表;生产成功 canonical 回填并刷新消费者 | loading、空、编辑、校验、saving、saved、unknown、版本/READY 冲突、无权限、0/1/500×50、swap、软停用历史 | 字辈与权限 | Task39 已建立唯一 GET/PUT、poemSetVersion/If-Match、完整候选集合、unknown 与 CORS owner 红灯;页面尚未接远端,见 2.17/5.16 |
| T01 | 世系树 | `pages/tree/t01-tree-overview` | 以当前成员为焦点阅读可扩展世系窗口,并在图、概览和线性列表间定位成员 | G01 快捷入口或 G05 | 返回来源;进入单实例 T03、T04、T06、T07 | 上二代/下二代初始窗口、搜索、四级 LOD、多配偶联合点、宽支系聚合、代际缺口、四类边界、图/列表、空、失败、版本冲突、500 节点性能;节点与线由同一 Canvas/矩阵/帧绘制 | 世系与成员 | 专项已核对:现有递归 `LineagePersonTreeView` 不满足;待后端按规范图窗口问题单更新 Apifox |
| T03 | 成员档案 | `pages/tree/t03-member-profile` | 在单个原生页面实例内查看成员资料、亲属与受控状态 | T01、T07、R02 的成员关联 | 页内成员轨迹优先返回;轨迹结束后回实际来源;进入 T05、T08 | A→B→C→B→A 页内轨迹、可编辑、隐私、无权限、成员缺失、加载失败、离世状态;读取成功后才推进轨迹 | 成员档案与权限 | 待对应业务阶段 OpenAPI 审查 |
| T04 | 新增亲属 | `pages/tree/t04-add-relative` | 录入首位成员或为目标成员新增亲属 | T01 指定节点或空树入口 | 当前只生成“尚未提交服务器”的本地预览,确认放弃后无结果回 T01;真实写成功后才定位新成员 | 首位成员、普通亲属、关系选择、必填、长摘要、保存中、本地预览、失败、放弃确认 | 成员与亲属关系 | 待对应业务阶段 OpenAPI 审查 |
@@ -245,9 +293,9 @@ T01/T03—T08 当前共用 `data/mock.js` 的唯一成员夹具 owner:列表
| T06 | 编辑关系 | `pages/tree/t06-edit-relationship` | 校正两个现有成员之间的关系 | T01 关系操作 | 当前只生成“尚未提交服务器”的本地预览,确认放弃后无结果回 T01;真实写成功后才刷新关系 | 成员选择、校验、冲突、循环关系、冲突规则弹窗、保存中、本地预览、失败、无权限 | 亲属关系与权限 | 待对应业务阶段 OpenAPI 审查 |
| T07 | 成员目录 | `pages/tree/t07-member-directory` | 搜索、筛选并选择家谱成员 | T01 成员目录入口 | 进入 T03;返回 T01 并恢复目录现场 | 完整列表、筛选、搜索无结果、明确空态、失败重试、加载、长列表、成员选择 | 成员查询 | 待对应业务阶段 OpenAPI 审查 |
| T08 | 成员状态 | `pages/tree/t08-member-states` | 解释成员隐私、纪念或无权限状态 | T03 人物状态入口 | 返回 T03;家谱不可用时回 G01 | 隐私隐藏、离世纪念、无权限、无效成员、权限变化 | 成员状态与权限 | 待对应业务阶段 OpenAPI 审查 |
| F01 | 家族动态 | `pages/family/f01-family-feed` | 按当前成员家谱展示动态并承载内容和档案入口 | 根 Tab 或 G05 | URL 已规范化后进入 F02—F04、F07、F10、R01、R03、R05、R10、R11 | 加载、列表、空、失败、无有效家谱、跨谱失败关闭 | 家族内容聚合 | 任务 7 已核对;线上有动态列表/写入能力,当前静态批次只读且按家谱隔离 |
| F01 | 家族动态 | `pages/family/f01-family-feed` | 按当前成员家谱展示动态并承载内容和档案入口 | 根 Tab 或 G05 | URL 已规范化后进入 F02—F04、F07、F10、R01、R03、R05、R10、R11 | 加载、列表、空、失败、无有效家谱、跨谱失败关闭 | 家族内容聚合 | Task40 已建立唯一 cursor GET、无 PII 投影、逐页鉴权与非泄露 404 红灯;后端绿前保持 fixture,见 2.18/5.17 |
| F02 | 发布动态 | `pages/family/f02-publish-feed` | 编辑文字或媒体动态的本地预览 | F01 发布入口 | 预览不发布、不产出结果;取消保留或确认放弃后回同一 F01 | 表单、空内容校验、长内容、媒体权限、本地预览、无权限、放弃确认 | 动态发布与上传 | 线上写请求要求 `feedContent` 且 minLength=1,另有 `feedType/mediaOssIds/sortOrder/status`;真实调用与媒体上传未启用,mock 以 `WRITE_UNAVAILABLE` 失败关闭,详见 2.11 |
| F03 | 动态详情 | `pages/family/f03-feed-detail` | 按 `genealogyId + feedId` 阅读动态并编辑评论草稿 | F01 动态卡 | 评论只在本页预览,不插入、不计数、不清空;返回同一 F01 | 加载、正常、内容失效、失败、评论校验、本地预览、无权限 | 动态与评论 | 线上评论写请求要求 `commentContent`,文档边界为 minLength=0/maxLength=1000;当前未调用写接口,不宣称发送成功,详见 2.11 |
| F03 | 动态详情 | `pages/family/f03-feed-detail` | 按 `genealogyId + feedId` 阅读动态并编辑评论草稿 | F01 动态卡 | 评论只在本页预览,不插入、不计数、不清空;返回同一 F01 | 加载、正常、内容失效、失败、评论校验、本地预览、无权限 | 动态与评论 | Task40 已拆分详情与一级评论 cursor owner;后端绿前保持 fixture,评论写仍只做未提交预览,见 2.18/5.17 |
| F04 | 谱文列表 | `pages/family/f04-article-list` | 按家谱分类、搜索和浏览谱文 | F01 谱文入口 | 进入精确 F05 或 create 模式 F06;返回同一 F01 | 加载、列表、分类、搜索无结果并重置、空、失败、长列表、新建权限 | 谱文 | 任务 7 已核对线上列表及写接口;当前列表由唯一只读 owner 提供深拷贝 |
| F05 | 谱文详情 | `pages/family/f05-article-detail` | 按 `genealogyId + articleId` 阅读并按权限进入编辑 | F04 谱文卡 | 返回同一 F04;有权限进入 edit 模式 F06 | 加载、正常、收藏暂未开放、编辑、失效、隐私、失败、无权限 | 谱文与权限 | 线上未证明收藏合同,当前禁用收藏;文章写请求要求 `articleTitle/articleContent` 且均为 minLength=1,详见 2.11 |
| F06 | 编辑谱文 | `pages/family/f06-article-editor` | 新建或编辑谱文的本地预览 | F04 新建或 F05 编辑 | create 回 F04edit 回精确 F05;预览不保存、不产出结果 | 新建、编辑、校验、加载、本地预览、失败保留、长正文、放弃确认 | 谱文编辑 | 线上 POST/PUT 已存在,`categoryId/coverOssId` 为 int64;真实调用未在导航批次启用,详见 2.11 |
@@ -275,7 +323,7 @@ T01/T03—T08 当前共用 `data/mock.js` 的唯一成员夹具 owner:列表
| M05 | 修改手机号 | `pages/profile/m05-change-phone` | 以当前密码重新认证,并通过受保护 TAC/6 位 OTP 验证新号码;门禁前保持本地预览 | M03 手机号入口 | 确定错误留页;最终 200/401/409/结果未知清本机回 A01;放弃确认回 M03 | 当前密码、新号、TAC、发送与倒计时、6 位码、占用、限流、提交中、并发、结果未知、进程终止 | 账号安全、短信与会话 | Task34 已建立专用 SaToken 发码、全活动六位码、ALL 会话撤销、outbox 与 credential marker 红灯;依赖 M04 raw wire,见 2.13/5.11 |
| M06 | 帮助中心 | `pages/profile/m06-help-center` | 从完整帮助列表搜索、分类并展开纯文本正文,无法解决时进入反馈 | M01 帮助入口 | 返回 M01;无法解决时进入 M07 | 加载、服务端空、分类、搜索无结果、展开、失败重试、认证失效、取消 | 帮助内容 | Task27 选定列表唯一 owner 并建立红灯;专用 required、纯文本、仅发布、顺序和认证语义待后端关闭,见 2.13/5.4 |
| M07 | 意见反馈 | `pages/profile/m07-feedback` | 通过唯一真实 owner 提交必填内容及可选类型、联系方式 | M06 联系入口 | 成功留页保留提交快照;编辑后可再提交;未提交修改放弃确认回 M06 | 必填、提交中、成功防重、失败、结果未知、mock 不可提交、未保存返回 | 用户反馈 | Task25 已接 `POST /genealogy/app/feedback` 严格客户端;remote 实测与 MuMu 待认证门禁关闭,见 2.13 |
| M08 | 应用推广 | `pages/profile/m08-promotion` | 明确说明邀请码服务当前不可用 | M01 推广入口 | 查看不可用说明;返回 M01 | 无可用邀请码、服务未接入、说明弹层 | 邀请与系统分享 | 线上无邀请码签发/校验/直入合同,已删除码值、复制和海报伪能力,见 2.13 |
| M08 | 邀请家人 | `pages/profile/m08-promotion` | 从 workspace 选择有正式邀请权限的家谱并管理单次活动票据 | M01 推广入口 | 签发、显示/复制/系统分享、撤销;返回 M01 | 加载、无权限、无票据、签发/unknown、原码恢复窗、即将过期、撤销/竞态、权限失效 | 家谱邀请与系统分享 | Task37 六 operation 邀请红灯见 2.15/5.14;通过前保持不可用且不展示假码 |
| M09 | VIP 与订单 | `pages/profile/m09-vip-orders` | 展示基础说明并明确订单服务当前不可用 | M01 服务入口 | 查看关闭说明;返回 M01 | 服务未开放、无订单数据、说明弹层 | 服务权益、订单与支付 | 套餐/订单端点存在但支付闭环未定义,已删除查询参数演示订单,见 2.13 |
| M10 | 关于家谱 | `pages/profile/m10-about-settings` | 从 manifest 展示版本和协议说明,并安全退出当前设备凭证族 | M01 设置入口 | 协议/退出取消留在本页;确认后立即清本机并回 A01,远端结果只更新一次性提示 | 版本、协议、隐私、退出确认、双击、pending、撤销确认/未确认/拒绝、账号竞态、根导航失败 | 配置、协议与认证 | Task9 已完成本机清理基线;Task32 已建立当前凭证族、幂等 200、logoutCoordinator、required RVoid 与部署复用红灯,见 2.13/5.9 |
@@ -355,15 +403,15 @@ PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relatio
**优先级:** P1;阻塞 G01/G05 切到 remote 和正式上线,不阻塞继续审查无依赖业务域。
**唯一 owner** `GET /genealogy/app/genealogies/mine` 持有当前账号可访问集合;`GET /genealogy/app/genealogies/{genealogyId}/overview` 持有 G05 只读展示。首批不接语义重复的 `GET /{genealogyId}`,不让两个详情响应互相补字段。
**唯一 owner** `GET /genealogy/app/genealogies/mine``appListMyGenealogies` 持有当前账号可访问集合;`GET /genealogy/app/genealogies/{genealogyId}/overview``appGetGenealogyOverview` 持有 G05 只读展示。两个 operationId 全局唯一,路径只开放 GET、无 body;首批不接语义重复的 `GET /{genealogyId}`,不让两个详情响应互相补字段。
**当前线上证据:** `RListAppGenealogyVo/RAppGenealogyVo/AppGenealogyVo` 均无 `required``AppGenealogyVo.genealogyId``integer/int64``roleType/status/memberStatus` 无 enum,且没有 `canView`。最大合法 int64 经 JavaScript JSON 解析会失真,解析后再转字符串无法恢复。无令牌调用 `/mine``/1``/1/overview` 均实测返回 HTTP 200、`application/json;charset=UTF-8``{code:401,msg,data:null}`;线上文档却只列 200/401401 schema 为 `*/* string`,也没有有效 security 声明。无令牌行为已经拒绝访问,因此不能仅凭注解缺失断言已发生公开泄漏;对象级授权仍没有有效账号反例证据。
**API-GENEALOGY-WORKSPACE-001:无损身份与最小 schema 闭包。** `/mine` 的 200 响应固定为 `RListAppGenealogyVo``/overview` 固定为 `RAppGenealogyVo`;两 envelope `code/data` 必填,列表 `data``AppGenealogyVo[]`,详情 `data` 为单个 `AppGenealogyVo`。首批实体必填字段固定为 `genealogyId/genealogyName/canView/canManage/canEditContent/roleType`ID、名称为 `minLength >= 1` 的字符串,三项 capability 为 boolean,角色为至少两个稳定非空值的正式 enum。地点、堂号、人数、简介等展示字段可选,响应不强制 `additionalProperties:false`。URL path 本身以文本传输,不因 JSON 响应问题机械强制改类型;真正必须改的是响应身份
**API-GENEALOGY-WORKSPACE-001:无损身份与最小 schema 闭包。** `/mine` 的 200 响应固定为 `RListAppGenealogyVo``/overview` 固定为 `RAppGenealogyVo`;两 envelope 都关闭额外字段且精确 required `{code,data}`、code=200,列表 data 为 `AppGenealogyVo[]`,详情 data 为单个 `AppGenealogyVo`。首批实体必填字段固定为 `genealogyId/genealogyName/canView/canManage/canEditContent/roleType`响应字段和 overview path 都引用词法 `GenealogyId`,名称引用 `GenealogyName`,三项 capability 为 boolean,角色为至少两个稳定非空值的正式 enum。地点、堂号、人数、简介等实体字段可选,`AppGenealogyVo` 不强制关闭未知额外字段
**API-GENEALOGY-WORKSPACE-002:可访问集合与能力投影。** `/mine` 中只有 `canView=true` 的行能进入 current context;也接受后端明确并由集成测试证明“只返回当前账号仍可查看 ACTIVE 家谱”的等价合同。G01 不能从未声明的 status 或 role 猜可访问性。`canManage/canEditContent` 是管理与内容入口的授权 UI 投影;真正写接口仍须后端逐次鉴权,capability 不能替代服务端授权。`roleType` 只拥有 G01 分组和角色标签,不替代 capability。
**API-GENEALOGY-WORKSPACE-002:可访问集合与能力投影。** `/mine` `x-current-account-viewable-only=true` 明确只返回当前账号仍可查看的家谱,并以 `OMIT_ONLY_AFTER_CONFIRMED_ACCESS_LOSS` 约束仅在成功确认失权时省略;G01 不能从未声明的 status 或 role 猜可访问性。`canManage/canEditContent` 是管理与内容入口的授权 UI 投影;真正写接口仍须后端逐次鉴权,capability 不能替代服务端授权。`roleType` 只拥有 G01 分组和角色标签,不替代 capability。
**API-GENEALOGY-WORKSPACE-003错误语义与对象级授权。** 认证失效、对象无权/撤权、家谱不存在和服务故障必须在同版本文档、runtime validator 与部署行为中稳定一致。后端可使用规范 HTTP 401/403/404/5xx,也可继续 HTTP 200+稳定业务码;不能出现文档写 HTTP 错误、部署只回无字典业务码的双合同。至少用两个账号执行无凭证、跨账号、撤权、删除、服务异常和正常访问反例G01 只在成功列表确认 ID 消失或明确撤权时写 tombstone,网络/5xx 保留现场G05 不把错误回退 fixture 权限。
**API-GENEALOGY-WORKSPACE-003typed HTTP、缓存与对象级授权。** 两条读取都 required SaToken 和非空 clientid`/mine` 精确 200/401/429/500overview 精确 200/400/401/404/429/500,不再允许 HTTP 200+业务错误。400 固定 `GENEALOGY_ID_INVALID`401 为 `AUTH_REQUIRED`,对象无权/撤权/不存在以 `NON_DISCLOSING_GENEALOGY_NOT_AVAILABLE` 统一 404429/500 分别为 `RATE_LIMITED/GENEALOGY_WORKSPACE_UNAVAILABLE`。全部只允许 JSON;每个响应必须含固定单值 private/no-store429 另有 Retry-After;只允许非语义 tracing header`traceparent/tracestate/x-request-id/x-correlation-id`,禁止 ETag 与其他语义 header。parameter/response/header/schema 只接受 exact local component ref;四种 tracing header 的引用也必须 exact local,内联 Header Object 必须 allowed-key 关闭并带非 null 的 string schema。clientid、code/data/businessCode、实体字段和 role 枚举以 allowed-key 集拒绝 `nullable`、readOnly/writeOnly、冲突组合和额外验证关键字;全局扫描全部 `/genealogy/app/` GET 的 200 schema 闭包并合并 allOf 属性,除 overview 外返回单谱实体的旁路详情一律失败,不把 `/mine` 数组误算为单谱读取。至少用两个账号执行无凭证、跨账号、撤权、删除、限流、服务异常和正常访问反例G01 只在成功列表确认 ID 消失时写 tombstone,网络/5xx 保留现场G05 不回退 fixture 权限。
**展示取舍:** 当前 G05 fixture 还显示 `source/manager/certification/ancestorName/parentName/branchName/updatedAt/activeCount`,线上没有等价字段。首批允许把这些区域隐藏或使用明确“待补充/待同步”的非业务降级;若产品要求继续把它们作为可信首屏信息,后端需另补字段或明确组合接口。不能为了复刻 mock 把 22 个字段全部列为当前硬门禁,也不能把 mock 值带进 remote。
@@ -530,3 +578,93 @@ PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relatio
**页面与关闭条件:** 地区搜索、PENDING、unknown、fatal/quarantined、committed、context/导航失败必须可见;默认 UNKNOWN,删除 1800/UTC 日期错误;label、radio、aria-invalid/describedby、首错聚焦、至少 44dp 原生按钮、AppDialog 焦点与状态播报同批实现。后端同版本 JSON/YAML 必须通过 `tests/g03-bootstrap-openapi-contract.ps1`,客户端实现必须另行通过会实际执行状态机套件的 `tests/g03-bootstrap-client-release-gate.ps1`;它们只是 G03 自身两门禁,真实开放还要求 Task26 workspace 读取门禁、聚焦/全量回归和 MuMu 原生字号、TalkBack、键盘、慢网、双击、杀进程及 Android 返回矩阵全部通过。
当前其余已知但尚未核实的重点依赖包括:微信登录、公共行为验证、完整短信状态机、邀请码验证与直接加入、结构化亲属关系、上级家谱与支系权限、管理员授权与功能开关、上传与系统权限、消息业务目标、系统分享、订单支付与退款。它们只表示审查重点,不预判后端一定缺失。
### 5.13 后端问题单 API-JOIN-001—006
**优先级:** P0;阻塞 G06/G08/G09/G10 普通申请、撤回和审核的真实闭环。邀请码直入不属于本问题单。
**API-JOIN-001:七个稳定 owner、词法身份与 cursor。** 发布设计 23.10 的七个 operationId;三个列表分别返回专用 page/item,不返回 totallimit 1—50cursor 绑定 tenant/account/client/filter;搜索按 updatedAtgenealogyIdmine/pending 按 submittedAtapplyId 稳定排序。genealogyId/applyId 均引用 1—128 位 URL-safe string owner;搜索 viewerState 只允许 NOT_JOINED/MEMBER/PENDING/REJECTED/FORMER_MEMBER/OWNER,不另发 canApply。
**API-JOIN-002:闭合投影与文本。** 申请体仅 `applicantName/relationDesc/applyReason?`,前两项 required;审核体仅 APPROVE 或 REJECTrequired rejectionReason 两分支。三项申请文本和拒绝原因共用 `JOIN_APPLICATION_TEXT_V1`。mine 使用四状态 discriminator且拒绝分支必有可见原因;pending 精确五字段并递归禁止手机号、用户、邀请人和审核人。删除旧 APP join DTO 和通用响应引用。
**API-JOIN-003:幂等、恢复、零写与准入协调。** request key 为 `gja.{issuedAt}.{CSPRNG}`600/300/120 秒边界;canonical identity 覆盖 method/path/genealogyId/tenant/account/client/body。parameter/response 只允许 exact local 分区引用;幂等 scope、活动唯一 scope、事务 effects、重验字段、状态迁移和 cursor 扩展必须是真实 JSON 数组,逗号字符串不能伪装集合。数据库唯一约束保证同账号同谱一个 PENDING;POST 在事务内重验 READY、PUBLIC_APPLY 与 applicationEligibility,并与 G11 设置 PUT 共享 `x-public-apply-coordination=ATOMIC_SINGLE_WINNER`,关闭公开申请与新准入竞态只允许一方提交。申请行与 SUCCEEDED receipt 同事务。新增纯读 status GET,三态转换和终态不可变;窗口内 404、窗口后零写 FAILED、跨主体非泄漏 404、迟到 POST 拒绝,客户端只持久无 PII marker。
**API-JOIN-004:撤回。** DELETE 以 PENDING CAS 决定唯一赢家;相同撤回只重放 WITHDRAWN typed 200,审核或其他终态获胜时返回 typed 409+最小 current state,不存在或越权统一 404。key 复用、key 过期与活动 PENDING 冲突不强制返回不存在的申请 current。
**API-JOIN-005:审核。** PUT 在事务内重验权限、READY、PUBLIC_APPLY,执行 PENDING CAS、唯一成员关系和终态;相同决定只重放 APPROVED/REJECTED 原 200,绝不返回 WITHDRAWN;相反决定或不同规范化拒绝理由返回 409。POST 的 409 精确引用 `RJoinApplicationKeyConflict`DELETE/PUT 的 409 精确引用 `RJoinApplicationStateConflict`,不得共享会接纳跨操作业务码的宽联合 owner。无 applicationVersion、If-Match 或详情端点。
**API-JOIN-006:安全与发布。** 所有响应精确 typed JSON、private/no-store429 和 status 窗口内 404 带 Retry-After;禁止 default、3xx、`*/*`、200 包认证错误、int64 和通用 RList/RObject/RVoid。后端以真实账号、数据库观测、并发与 fault injection 证明 cursor、唯一约束、fencing、零写、事务、跨主体不泄漏和正式 HTTP/CORS;OpenAPI 静态扩展不替代实现证据。
**关闭条件:** 同版本双导出通过 `tests/join-application-openapi-contract.ps1`;再完成后端运行时反例、客户端 marker/status/CAS 收敛测试、旧 adapter/手机号/消息承诺/LOCAL_WITHDRAWN 原子删除、全量回归和 MuMu 320/360/412、系统字号、TalkBack、键盘、返回、双击、慢网、断网、杀进程矩阵。当前门禁输出 `JOIN-APPLICATION-OPENAPI-CONTRACT BLOCKED`,页面保持本地预览。
### 5.14 后端问题单 API-INVITE-001—006
**优先级:** P0;阻塞 M08 邀请签发、G06 邀请码解析与直接加入。普通申请继续由 API-JOIN 唯一拥有。
**API-INVITE-001:六个专用 owner 与流程隔离。** 发布 23.11 的 list/issue/revoke/resolve/redeem/redemption-status 六 operationrequired SaToken/clientid、词法 genealogy/ticket/membership ID、operation-specific typed responses。G06 直接确认兑换,G08 只保留普通申请;普通申请 body 反向禁止 inviteCode/ticket/token。禁止平行旧入口、通用 promotions、礼仪邀请或普通申请 POST 承担本域。
**API-INVITE-002:单次秘密票据。** 原码固定版本前缀+26 位 Crockford Base32 随机量、至少 128 位熵、24 小时单次使用;HMAC 查找,KMS 密文仅保留 600 秒幂等恢复窗。列表只返回最多 20 个活动元数据;原码只允许 issue 200 指定位置,redemption token 只允许 resolve 200 指定位置,并对所有响应 `$ref/oneOf/allOf/items` 递归检查秘密与 PII 闭包。
**API-INVITE-003:签发、权限与撤销。** issue 无 body`gii` key 按 method/path/genealogy/tenant/account/client 唯一;已存在 key 不受首次接受窗影响,不同 digest 优先冲突,600 秒内返同码、窗后返同 ticket metadata`ISSUED_SECRET_UNAVAILABLE` 且不新建。ticket、HMAC、KMS 密文与 request receipt 原子提交。workspace `canInviteMembers` 唯一映射服务端 `INVITE_MEMBER`list/issue/revoke 均重验并只管理 actor 自己签发的票据,跨 issuer 404;活动上限由数据库约束。DELETE 以 ACTIVE→REVOKED CAS,相同撤销重放 200CONSUMED/EXPIRED typed 409 current;撤销与兑换只有一个赢家。
**API-INVITE-004:安全解析与原子兑换。** resolve 的原码只在 JSON body,不消费票据或建立成员但可轮换唯一短时 grant 摘要,并统一 `INVITE_TICKET_NOT_AVAILABLE`、多维限流与时间侧信道控制;返回最多五分钟且绑定 actor/client/ticketVersion/genealogy/authorizationEpoch 的敏感 token。redeem 无 body,事务谓词包含 token、版本、数据库时钟过期、unused、READY、功能、签发者权限、账号资格和容量;票据消费、唯一 MEMBER 与回执同事务。ALREADY_MEMBER、活动普通申请、账号禁用或票据失效均不得消费票据。
**API-INVITE-005:幂等与 unknown 恢复。** `gir` key 的 canonical identity 包含 token identity/ticket/genealogy/actor/client;同 key 不同 identity typed 409。600/300/120 秒边界、control claim、fencing/watchdog 和纯读 status 固定 PENDING/SUCCEEDED/FAILED_NO_COMMIT;窗口内 404 带 acceptUntil/Retry-After,窗口后零写计算 FAILED,跨主体统一 404。已存在终态优先,终态回执至少保留 30 天且晚于 30 天客户端 marker 清理;清理后不得据 absence 否定领域写。客户端只持久无秘密 marker,成功后只重试 workspace/context/导航本地收口。
**API-INVITE-006:安全、无障碍与发布。** 所有响应 JSONprivate/no-store429 带 Retry-AfterURL/query/storage/log/APM/分析/崩溃报告/自动测试和发布证据截图零秘密,用户主动显示、复制或系统分享必须先提示风险且不得自动触发。后端以真实数据库并发、fault injection、权限降级、猜码、两人同码、同人两码、撤销竞态、日志审计与 HTTP/CORS 证明实现,静态扩展只登记意图。客户端原子删除 `JP2026`、fixture target、invite→G08 与 G08 invite 分支;补原生按钮、44dp、tab/label/aria、状态播报、长码换行、冻结和 MuMu 全矩阵。
**关闭条件:** 同版本双导出通过 `tests/invite-ticket-openapi-contract.ps1`,再通过后端运行时反例、客户端 coordinator 测试、Task36 普通申请隔离、workspace/context 收口、全量回归及 MuMu 320/360/412、1.3 倍字号、TalkBack、键盘/粘贴、双击、慢网/断网、杀进程、Android 返回和系统分享取消。当前输出 `INVITE-TICKET-OPENAPI-CONTRACT BLOCKED`,页面不得接假接口。
### 5.15 后端问题单 API-SETTINGS-001—006
**优先级:** P0;阻塞 G11 家谱设置真实读取、并发写入、访问规则切换与发布。
**API-SETTINGS-001:唯一 read/write 与共享字段 owner。** 保留全局唯一 `appUpdateGenealogySettings` PUT,递归拒绝旁路 settings 写、任一 `genealogyName/intro/accessPreset` 单字段第二 body owner以及外部/错分区同名 `$ref``/genealogy/app/genealogies...` 写 body 根层 `intro` 明确保留给设置,子域使用自身字段名。删除 generic GET 且不新增 PATCHoverview 是 G11 canonical baseline 唯一读取 owner,并由 workspace 门禁以 schema 闭包与 allOf 合成排除旁路详情。发布共享非 null `GenealogyName/GenealogyIntro/GenealogySettingsVersion`:名称拒绝换行/控制字符,简介只允许内部 LF并拒绝 CR/tab/其他控制字符,让 bootstrap、settings body 与 `AppGenealogyVo` 引用同一 24/80/版本合同;`GenealogyAccessPreset` 继续由共享 owner 唯一持有且禁止 nullable。200 复用关闭额外字段的 `RAppGenealogyVo`,不建重复 snapshot。
**API-SETTINGS-002:原子 dirty-only merge。** body 关闭额外字段、1—3 个出现字段,省略保持、intro 精确空串清空、null/空白/旧 pair/其他设置全部早失败。事务内锁定并重验 tenant、资源、权限,只应用出现字段;任一字段或迁移前置失败整笔零写。canonical no-op 保留版本且不产生额外领域副作用。
**API-SETTINGS-003:版本 CAS 与非泄漏权限。** `AppGenealogyVo.settingsVersion` 和 If-Match 共用 1—128 位 opaque owner,并以 `x-version-scope-fields`/`CANONICAL_SETTINGS_CHANGE_ONLY` 限定只在 canonical 设置实际变化时更换;旧版本精确 409+current,不发布 412/ETag 双轨。只有仍有权限者可得到版本/current;跨主体统一 404,权限撤销 403。冲突 precedence 为 version→READY→active pending409 wrapper 只含 discriminator/oneOf 且分支均为 exact local ref,正式 H5 CORS 允许 If-Match。
**API-SETTINGS-004:访问迁移与申请串行化。** 活动 PENDING 只阻断真实 PUBLIC_APPLY→MEMBER_ONLY,返回无 PII 的 `ACTIVE_PENDING_APPLICATIONS` 并让客户端引导 G10;不阻断其他字段/no-op,不自动拒绝或撤回,不改变邀请合同。设置 PUT 与任务 36 新申请 POST 都在事务内重验 genealogyState/accessPreset,申请另验 eligibility,并共享 `x-public-apply-coordination=ATOMIC_SINGLE_WINNER`;竞态只允许一方提交,不把合同绑死为某种数据库隔离级别。
**API-SETTINGS-005typed 响应、unknown 与缓存。** 精确 200/400/401/403/404/409/422/429/500,只允许 JSON;每个 response 必须含单值 enum private/no-store429 另有 Retry-After;只允许非语义 tracing header`traceparent/tracestate/x-request-id/x-correlation-id`,禁止 ETag 与其他语义 header,所有 component `$ref` 必须 exact local。四种 tracing header 若内联,只接受 allowed-key 关闭且带非 null 的 string schema 的 Header Object。标量、引用字段、成功/错误 envelope 与 fieldErrors 以 allowed-key 集关闭 `nullable`、readOnly/writeOnly、冲突组合和额外验证关键字;409 使用纯 businessCode discriminator 三分支,422 只返回三个字段错误,500 明示 outcome unknown。后端与客户端用真实账号、数据库观测、并发和 fault injection 验证零部分写、版本、权限与申请竞态;unknown 只以 fresh overview 对账,成功失效 mine/overview/search/preview 并重验 context。
**API-SETTINGS-006:客户端、无障碍与发布。** 同版双导出绿后,客户端首写先建可执行生产模块的纯 Node 失败测试,再实现 adapter/coordinator 并原子删除 fixture pair、timer、本地成功和 API 禁用旧断言;禁止恒红、token 扫描或测试内参考实现冒充行为证据。G11 覆盖原生 button/radio、label、aria-invalid/describedby、首错聚焦、busy/live、44dp、长文换行、返回冻结和冲突/unknown 草稿保留。OpenAPI、workspace/shared owner、客户端行为门禁、全量回归及 MuMu 原生矩阵任一未绿都不得接远端。
**关闭条件:** 后端从同一版本重导 JSON/YAML 并同时通过 workspace、join 与 `tests/g11-settings-openapi-contract.ps1`,再完成真实权限/版本/待审竞态/fault injection/HTTP/CORS;客户端随后按测试先行完成可执行 coordinator、页面/缓存/context 原子迁移,最后通过聚焦/全量和 MuMu 320/360/412、1.3 倍字号、TalkBack、软键盘、双击、慢网/断网/杀进程、Android 返回、权限撤销与版本竞态。当前输出 `G11-SETTINGS-OPENAPI-CONTRACT BLOCKED`48 项缺口;受保护 OpenAPI 未修改。
### 5.16 后端问题单 API-POEM-001—006
**优先级:** P0;阻塞 G12 字辈集合真实读取、原子保存、并发恢复、T01 正式消费与发布。
**API-POEM-001:唯一 owner 与 canonical 模型。** 发布唯一 `GET/PUT /genealogy/app/genealogies/{genealogyId}/generation-poems``appGetGenerationPoemSet/appUpdateGenerationPoemSet`,删除 collection POST、逐行 PUT、batch preview/save、management 及旧 DTO/response;不新增 preview token/status。GET/PUT 200 共用 closed `RGenerationPoemSetSnapshot → GenerationPoemSetSnapshot`,精确 required genealogyId/poemSetVersion/itemsACTIVE-only 且按 generationNo 升序;GET 重验 canViewPUT 事务内重验 tenant/canEditContent/READY。
**API-POEM-002:完整候选、软停用与原子写。** body 唯一为 closed required `AppGenerationPoemSetUpdateBody {items,disableMissing}`。items 0—500 严格 generationNo 升序;已有 ID 只允许 baseline ACTIVE 且可显式 move,新行省略 IDdisabled/unknown ID 失败,重复文本允许。false 保留遗漏 baselinetrue 软停用;empty false no-opempty true 停用全部。先校验声明严格升序,再构造声明目标与完整 merged candidate,随后按固定步骤验证唯一/slot/连续/最终容量、分配并校验新 ID,最后原子写;`GENERATION_SLOT_CONFLICT` 与所有校验失败零写,swap 必须显式声明全部受影响行,历史绝不物理删除。
**API-POEM-003:版本 CAS 与 Unicode。** `GenerationPoemSetVersion` 是 1—128 位 opaque、不可解析、永不复用的 ACTIVE 语义版本,PUT required If-Matchbody 无版本,禁止 ETag/412;no-op 保持版本,实际变化生成新版本。poemId/version 均保持词法字符串。`GenerationPoemText` 为 NFC、1—50 Unicode code point、well-formed UTF-16、无边界空白,拒绝控制字符、CR/LF/tab、bidi override/isolate/mark、zero-width/BOM/word joiner、行段分隔符和孤代理;generationNo 是 1—2147483647 的唯一排序 owner。
**API-POEM-004typed HTTP、security 与部署 CORS。** GET 精确 200/400/401/404/429/500PUT 另有 403/409/422;全部 typed `application/json`、private/no-store429 带 Retry-After,禁止 default/3xx/`*/*`/通用 envelope/200 包业务错。SaToken 精确为 Authorization header apiKeyoperation security 是数组且 scopes 为空,clientid required 非空。顶层 closed `APP_GATEWAY_PREFLIGHT` policy 精确允许四个请求头、GET/PUT/OPTIONS、部署 allowlist 与 600 秒;GET/PUT 引用它。后端必须用正式 H5 origin 的真实 OPTIONS/preflight、凭证、拒绝 origin/header/method 反例证明 gateway 实现。
**API-POEM-005:冲突、unknown 与缓存收口。** 409 只含版本变化+current 与 READY 两分支,422 只含可达请求/候选错误。网络、超时、408、取消、5xx 和畸形 2xx 是 unknown;客户端冻结 baseline/version/declared/disableMissing/sessionEpoch,构造 effective target 后 fresh GET。existing/NEW、同基数、无额外行和版本策略必须按 `FRESH_GET_THREE_WAY_NO_AUTO_PUT` 执行;current==target 优先且不归因,current==old 才可由用户明确重试,其他进入 divergent,不自动 PUT。成功 canonical 回填并失效字辈消费者缓存;权限、READY 或账号变化失败关闭。
**API-POEM-006:客户端、无障碍与发布。** 同版双导出通过主门禁、zero issues 对抗合同和 Unicode Node 冒烟后,客户端首写先建直接执行生产 normalizer/coordinator 的失败测试,再实现唯一 adapter/coordinator,并原子删除 fixture、timer、本地成功、旧 preview/save 断言与兼容分支。页面覆盖清空确认、loading/empty/editing/saving/saved/unknown/conflict/no-permission、live region、首错聚焦、44dp 和返回冻结;MuMu 覆盖 320/360/412、字号、TalkBack、键盘、0/1/500×50、swap、双击、断网/杀进程、权限撤销、READY 与账号切换。
**关闭条件:** 后端同版本重导 JSON/YAML 并通过 `tests/g12-generation-poem-openapi-contract.ps1`、对抗合同、真实 DB/并发/fault injection/HTTP/CORS;客户端随后通过生产行为测试、页面原子迁移、聚焦/全量回归与 MuMu 矩阵。当前输出 `G12-GENERATION-POEM-OPENAPI-CONTRACT BLOCKED``Issues: 72``G12-GENERATION-POEM-OPENAPI-ADVERSARIAL-CONTRACT PASS``G12-GENERATION-POEM-UNICODE-RUNTIME-SMOKE PASS` 只证明门禁质量,不解除后端红灯。受保护 OpenAPI 未修改。
### 5.17 后端问题单 API-FEED-READ-001—006
**优先级:** P0;阻塞 F01 动态列表和 F03 动态详情/一级评论的生产读取。发布、点赞、评论/回复写及媒体读取不属于本问题单。
**API-FEED-READ-001:三个稳定 owner 与旧入口删除。** 发布谱内嵌套的 `appListFamilyFeeds/appGetFamilyFeed/appListFamilyFeedRootComments`,全局各唯一;删除两个 `/page` GETcollection GET 使用 cursor。保留 `(genealogyId,feedId)` 复合资源边界,不新增可枚举的全局 feed 路由,不用写方法或 replies 冒充读取 owner。
**API-FEED-READ-002:词法身份与无 PII 最小 projection。** GenealogyId/FamilyFeedId/FamilyFeedCommentId 均为 opaque string。Feed 只含 `feedId/feedContent/authorDisplayName/publishedAt/hasMedia`root comment 只含 `commentId/commentContent/authorDisplayName/publishedAt`closed、required、非 null、有界。排除手机号、账号/业务用户 ID、tenant、status、remark、审核字段、parent/level、删除占位、内部媒体 ID 与持久化字段。`hasMedia` 只触发诚实占位;title/tag/like/count 不从 fixture 或正文猜测。
**API-FEED-READ-003:对象级授权与非泄露 404。** 每次/每页 fresh 验 tenant、genealogy、membership;详情和评论再验 feed 属于 path genealogy 且可见,评论只读正常 root。未知、错谱、删除/隐藏、撤权、跨主体或 cursor 跨 scope 统一 `FAMILY_FEED_NOT_AVAILABLE`,禁止 403 分流资源存在性;未认证才返回 401。
**API-FEED-READ-004keyset cursor 与读取窗口。** `cursor? + limit?`,默认 20、1—50,无 total/pageNumnextCursor 缺席即结束。feed 用 publishedAt/feedId 倒序,root comment 用 publishedAt/commentId 正序,时间不可变、ID ordinal tie-break。cursor 绑定 actor/session/client/scope/projection/order/limit/windowUpperBound/lastTuple,篡改或过期 typed 400。`UPPER_BOUND_KEYSET_LATEST_VISIBLE` 排除刷新前新增,后续删除/隐藏省略、编辑返回最新可见内容,不伪称严格 snapshot。
**API-FEED-READ-005typed HTTP、安全与部署。** 三 GET 精确 200/400/401/404/429/500,只允许 closed typed application/json、private/no-store429 Retry-After。SaToken 必须是数组且空 scopesclientid required 非空;禁止 int64、通用 RList/RObject/PageResult、default/3xx/`*/*`/HTTP 200 业务错。复用 `APP_GATEWAY_PREFLIGHT`,正式 OPTIONS/preflight 与拒绝反例由部署证明。
**API-FEED-READ-006:证据与客户端激活。** 主门禁先以 zero issues 合法种子和 ref/大小写/HTTP 方法/PII/int64/offset/404 分流对抗变异证明自身,再要求后端同版本 JSON/YAML/live、真实权限撤销/跨谱 cursor/并发可见性/HTTP/CORS。后端绿后首写必须是执行生产 read normalizer/coordinator 的 Node 失败测试,再原子迁移 F01/F03,删除 fixture 锁、嵌入 comments、伪重试和旧 fixture 断言;远端失败不回退 mock。完成 sessionEpoch、局部错误、媒体占位、焦点/滚动、无障碍和 MuMu 矩阵后才可发布。
**关闭条件:** `tests/family-feed-read-openapi-contract.ps1`、对抗门禁、后端运行时反例、同版本三份接口证据、客户端生产行为测试、聚焦/全量回归与 MuMu 矩阵全部通过。当前输出 `FAMILY-FEED-READ-OPENAPI-CONTRACT BLOCKED``Issues: 85``FAMILY-FEED-READ-OPENAPI-ADVERSARIAL-CONTRACT PASS MUTANTS=58` 只证明门禁质量,不解除后端阻塞。受保护 OpenAPI 与 F01/F03 页面未修改。