# 接口与页面映射总表 > 更新日期: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 差异核对;其余操作仍待逐项审查 ## 一、权威边界 本文件是活动页面、业务目标、进入方式、返回或完成目标、适用状态与接口归属关系的唯一总表。`pages.json` 是活动路由的唯一注册清单;两者必须同轮更新并保持精确一致。 接口的唯一编辑源是后端维护的 Apifox 项目。根目录 `APP.openapi.json` 用于离线自动分析,`APP.openapi.yaml` 用于人工阅读与跨工具导入;部署地址的 `/v3/api-docs` 只提供当前线上实现证据。阶段 0 保护的双导出是 OpenAPI 3.0.1、112 路径、153 操作的旧快照;2026-07-22 21:52 只读核对 `https://backend-api.ddxcjp.cn/v3/api-docs` 得到 OpenAPI 3.1.0、722 路径、858 操作、507 模型。发现差异时必须由后端生成同版本双导出,不能手工覆盖受保护文件,也不能把专项结论扩张为全部线上操作均已审查。 当前边界如下: - `pages.json` 注册 `52` 条活动路由;A02 已并入 A01,A06 保留源码但不属于活动路由。 - 全项目响应式迁移与统一扫描已经完成;退役通用页面和零消费者旧表单删除后,实际 `64/64` 个 Vue 文件均在覆盖清单中。 - 任务 5 实施前,A 系列及 G01—G10 曾由用户在 MuMu 中人工确认,G11、G12 以及 T、F、R、N、M 页面也曾逐页、逐状态审核并修复;这只是历史视觉基线,不覆盖任务 5—6 后的 G/T 动作、文案、权限和布局变更。任务 4—6 的当前代码仍须按实施计划重新完成 MuMu 流程矩阵。 - 上述视觉结论是当前继续工作的基线,不等于真实接口、持久化、系统权限、真实短信、微信、支付或跨页数据闭环已经完成。 - 后续发现明确、可复现的样式、交互或业务问题时可以重新打开页面;不得因为旧结论写着“通过”就忽略证据。 ## 二、全局业务与交互合同 ### 2.1 账户、启动与登录 - APP 不设游客模式。首次打开、无有效凭证或凭证过期时进入 A01;有效登录态进入 G01。 - A01 是唯一活动登录页,承载短信登录、受后端合同阻塞的密码登录入口、注册、找回密码、协议入口和尚未接入的微信登录入口。当前默认短信登录;密码登录在后端能够强制消费 TAC 票据前保持可见但不可用。登录成功统一到 G01,不直接恢复上次浏览的深层页面。 - 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` 的 `8–32+字母数字` 旧策略;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 已实现可发送、发送中、60 秒倒计时、失败重试、手机号变更失效、验证码到期和重复发送保护;短信码精确为 4 位。认证 HTTP 只接受 HTTP 200 严格 envelope,统一 15 秒超时,Android 返回或页面卸载会中止 RequestTask,迟到回调不得改变已离开页面。真实限流、前后台剩余时间恢复和多设备重放仍需后端集成与 MuMu 证明。 - 凭证过期先回 A01,再显示项目自定义的单按钮信息弹窗并聚焦登录表单;主动退出清除凭证,但可以保留用户上次选择的密码或短信登录方式,不保存密码。 ### 2.2 家谱上下文与加入、创建 - 一个账号允许创建或加入多个家谱。G01 列表按“我创建的”“我加入的”“加入申请”分组,顶部只表示当前选中项。 - G01 顶部当前家谱卡用于打开切换层;下方可用家谱卡直接进入 G05。只有导航成功后才同步顶部选中项和本机当前家谱 ID,失败或并发点击不得把页面与持久上下文串到不同家谱。首次且没有历史选择或失效标记时可确定使用首个可用家谱;调用方给定的新列表不再包含历史 ID,或显式目标不可用时,必须清空、持久标记并要求用户重选,跨重载也禁止静默回退到另一家谱。只保存词法字符串 ID,不复制整份家谱数据;真实撤权检测等待 workspace 接入 onShow/事件刷新。 - 审核中记录只展示进度和“撤回申请”,顶部卡不可进入家谱;被拒绝记录展示原因和“修改后重新提交”,进入 G08 而不是 G05;已退出或被移除记录展示原因和“重新申请”,不得访问原家谱内容。 - 只有 READY 且当前账号 `canView=true` 的家谱能够成为全局家谱上下文;审核中、被拒绝、已退出或被移除的记录不得覆盖最近一个可用上下文。G03 新合同不再产生“待录入始祖”业务状态;历史半成品如真实存在应由后端迁移/隔离,不能继续成为客户端正式状态。 - 没有可用家谱时,相关页面不得展示上一个失效家谱的缓存内容,应引导用户搜索家谱、使用邀请码或创建家谱。 - G01 空态的主次顺序为搜索家谱、邀请码加入、创建家谱;非空列表保留“添加家谱”底部弹层。所有者可见世系、成员、字辈诗、申请审核四个快捷入口,普通成员不显示申请审核。 - G06 同时承载搜索与邀请码定位。产品目标仍是“邀请码直接加入且不生成审核记录”,但当前 OpenAPI 没有邀请码校验或直接加入操作,因此本地流程只展示目标并进入 G08 填写确认,完成后无结果返回 G01,不能选中家谱或声称已经加入。搜索申请进入审核;真实邀请码终点必须等后端合同补齐后替换本地预览。 - G06 结果至少需要谱名、姓氏、地区、堂号、所属上级谱、当前支系、管理者或认证信息、成员规模和最近更新时间,以区分同名家谱和支系;未加入、已加入、审核中、被拒绝、已退出或移除、我创建的六种关系各自只出现一个明确动作。 - G08 当前用真实姓名、与已知长辈的文字关系和补充说明表达申请;用户可见示例统一使用“某某某堂侄”等通用占位,不出现具体姓名。后端提供结构化参照成员或关系字段后,应以新合同完整替换文字关系旧路径。 - 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 世代缺口时停止保存,不能静默错位。 ### 2.3 页面状态与请求结果 - 每个可请求页面至少判断正常、加载、空、失败、无权限和数据失效是否适用;表单另覆盖本地校验、提交中、成功、失败、取消和重复提交。 - 本地校验先于请求;字段错误就近展示。关联错误必须关联并聚焦到真正相关的字段,页面级或系统级错误在提交区或自定义结果弹窗中说明。 - 提交开始后锁定同一动作,避免重复写入;失败保留用户输入并提供明确重试;成功先完成必要数据刷新,再结束当前流程。 - 列表进入详情后返回,应恢复滚动位置、搜索词、筛选条件、展开分组、已加载页数和当前家谱选择;只有主动刷新、账号切换或原数据失效时才重置。 - 目标数据过期、权限变化、网络失败和取消不是空数据,必须分别呈现可理解的结果和下一步。 - 分页加载必须保留已有内容和滚动位置;失败显示就地重试,结束显示明确末尾状态,数据不足一页时不制造虚假“到底”文案,也不循环触发。 - 下拉刷新保留原列表,成功后只在确有变化时提示更新;失败在列表顶部提供重试,不把刷新和触底加载混成同一状态。 - 空态必须区分首次使用、搜索或筛选无结果、确实没有内容、加载失败和无权限;每种空态只突出一个主操作,最多一个次操作。 ### 2.4 导航、弹层与流程终点 - 当前三个业务根页面是 G01“家谱”、F01“家族”和 M01“我的”,A01 是认证根页;导航栈语义统一已经完成,路由注册表、导航网关、共享页头、自定义底栏、认证、G、T、F、R、N/M 系列活动页均按测试先行落地,退役通用页面、临时页面目录和最后一个零消费者旧表单组件均已删除。源码导航扫描为 `MIGRATION-DEBT=0`;认证至 N/M 的 MuMu 原生流程复核仍待执行。 - 返回、取消、完成和重复进入必须分别验证。页面完成后不得把已经结束的旧流程继续留在栈中,也不得用 `navigateBack` 猜测一个可能不存在的返回目标。 - 普通底部弹层可由遮罩或 Android 返回键关闭;存在未保存输入时先确认是否放弃。确认弹窗的返回键等同取消;任何取消都不得被记录为成功。 - 弹窗高度只允许使用视口 `max-height` 和内部滚动;普通页面内容高度由内容决定,不为单一设备压缩字号、行高或控件尺寸。 - 用户可见反馈使用项目自定义组件,不新增原生 UniApp Toast、Modal、Loading 或 ActionSheet 作为正式体验。 - 轻提示、底部弹层、居中确认、结果说明和危险操作按决策成本分级。不可逆操作必须说明后果并二次确认;普通操作不滥用确认。 - 产品合同已经定案为“邀请码直接加入且不生成审核记录”:只有未来真实邀请码校验与直接加入接口成功才允许建立成员关系。当前 G06/G08 只做本地流程预览并明确未提交服务器;M08 的旧审核分支、硬编码邀请码、复制和海报伪能力已经删除,在真实邀请码签发与校验合同落地前保持不可用。 ### 2.5 三个根页面与主要流程 ```text A01 登录/A04 注册 → G01 我的家谱 → 搜索或邀请码加入(G06 → G08 → G09 或 G01) → 创建家谱(G03 创建 → G03 录入始祖 → G05) → 家谱浏览与管理(G05 → T01/G10/G11/G12) G01/G05 → F01 家族内容 → 动态、谱文、相册、人物、礼仪、备忘与功德 G01/M01 → N01 消息中心 → N02 消息详情 → 对应业务目标 M01 我的 → 资料、安全、帮助、反馈、推广、服务与关于 ``` ### 2.6 当前本地数据与路由参数边界 当前 `52` 条活动页面除 A01/A04/A05 的认证调用外,仍使用页面内本地状态、fixture 或 mock 数据;其他活动页面没有 `appApi` 消费者。认证调用已经对准真实端点,但 `runtimeConfig.mode` 固定为 `mock` 并失败关闭,不能解释为真实后端已经联通。以下只记录页面源码当前主动读取的查询参数;上游传入但页面未读取的参数属于待审债务,不能写成有效合同。 | 页面 | 当前主动读取的查询参数 | | --- | --- | | A01、A04、A05 | 无 | | G01 | `genealogyId`、`state` | | G03 | 无 | | G05 | `genealogyId`、`state` | | G06 | `mode`、`state` | | G08 | `genealogyId`、`source`、`state` | | G09 | `state`、`status` | | G10 | `genealogyId`、`state` | | G11 | `genealogyId`、`state` | | G12 | `genealogyId`、`state` | | T01 | `genealogyId`、`state`、`selectedId` | | T03 | `genealogyId`、`personId`、`state` | | T04 | `genealogyId`、`personId`、`mode`、`state` | | T05、T06 | `genealogyId`、`personId`、`state` | | T07 | `genealogyId`、`state` | | T08 | `genealogyId`、`personId`、`state` | | F01 | `genealogyId`、`state` | | F02 | `genealogyId`、`state` | | F03 | `genealogyId`、`feedId`、`state` | | F04 | `genealogyId`、`state` | | F05 | `genealogyId`、`articleId`、`state` | | F06 | `genealogyId`、`articleId`、`mode`、`state` | | F07 | `genealogyId`、`state` | | F08、F09 | `genealogyId`、`albumId`、`state` | | F10 | `genealogyId` | | R01 | `genealogyId`、`state` | | R02 | `genealogyId`、`mode`、`personId`、`state` | | R03 | `genealogyId`、`state` | | R04 | `genealogyId`、`mode`、`relativeId`、`state` | | R05 | `genealogyId`、`state` | | R06 | `genealogyId`、`ceremonyId`、`state` | | R07 | `genealogyId`、`mode`、`ceremonyId`、`state` | | R08、R09 | `genealogyId`、`personId`、`state` | | R10、R11 | `genealogyId`、`state` | | N01 | `genealogyId`、`state` | | N02 | `id`、`state` | | M01 | `state` | | M02—M08、M10 | 无 | | M09 | `state` | `state` 参数目前只用于直接加载页面时的本地状态审查,不代表后端请求字段;导航网关不注册也不会生成这个展示钩子。R 系列的 `count/saveResult/personName/giftId/ritualId` 已全部退役,人物名称和实体资料只能由受校验的复合身份从唯一只读 owner 取得。G03 的 `step/genealogyId`、G05 的 `mode/role/genealogyName`、G08 的 `previous/genealogyName` 以及 G12 的 `startGeneration/currentGeneration` 已从页面和注册表删除:页内步骤留在页面状态,名称按词法 `genealogyId` 从唯一 fixture 或后续领域数据取得,来源只由真实栈与受验证 `sourceKey` 表达。T03 初始 `personId` 是不可变宿主页路由身份,亲属浏览只改变页内活动成员和轨迹;因此当前 T05 本地预览必须以 `goBack()` 回到原实例,不得用活动成员重写 T03 URL。其余参数仍须在对应业务阶段判断为真实输入、页内状态、领域数据或删除项,不得把调试参数固化成接口合同。 T01/T03—T08 当前共用 `data/mock.js` 的唯一成员夹具 owner:列表查询必须传 `genealogyId`,单成员查询必须同时传 `genealogyId/personId`,返回值与嵌套亲属均为快照。错误家谱下的已知成员、未知成员或缺失必填身份不得回退到 1001、首位成员或“待核实成员”;T04 只有 `mode=first` 且 `personId` 为空时可以建立首位成员草稿。该夹具只支撑当前本地设计流程,不代表后端字段已经完整;任务 18 接入真实接口时必须删除临时 owner 与选择器,而不是并存第二份成员合同。 ### 2.7 全局非功能门槛 - 默认字号和约 `1.3` 倍系统字号下,超长谱名、生僻姓名、错误说明和主操作不得重叠或丢失关键含义。 - 主要触控目标不小于约 `44dp`;点击后 `100ms` 内出现反馈,预计超过 `300ms` 的操作显示明确加载状态。 - 长列表至少用 `500` 条 mock 数据验证分批渲染、稳定 key、刷新、分页、末项可达和返回现场恢复。 - 同一详情连续进入和退出 `20` 次,并快速切换根页面、重复开关弹层;不得出现白屏、串状态、重复堆栈、残留遮罩或逐次变慢。 - Android 性能以约 `4GB` 内存的中低端设备为底线,验证启动、键盘、长列表、图片解码、页面切换和系统返回手势。 - 页面离开时清理本页创建的定时器、监听器、上传任务和动画状态;连续使用不得积累重复请求或实例。 - 当前导航阶段已完成任务 1—10 的共享基础、组件、退役入口、认证、G、T、F、R、N/M 系列静态迁移和零债务门禁;A01/A04/A05 的 TAC 客户端、领域上下文与 M07 反馈客户端也已完成,MuMu 原生矩阵尚未执行。T01、认证与家谱工作区后端门禁并行等待外部合同关闭;跨页面领域数据持久化以工作区门禁为前置,本地先按无依赖业务域、全局文字层级和无障碍第二轮逐批推进,不得一次混合实施。 ### 2.8 G 系列第一轮接口差距账本 本轮已逐页核对 G01、G03、G05、G06、G08—G12 与受保护双导出,并对照新线上 OpenAPI 做了第一轮差异检查;随后任务 26 又对 G01/G05 做了三人反向质询、部署探测与失败门禁,但没有在 schema owner 未定时把页面接到 `appApi`。所有 G 页面仍是本地交互预览,页面显示角色也只是 fixture 的最小权限投影,不代表服务端授权。以下问题在真实接入前必须由接口适配合同或后端同版本新导出关闭: - 公共边界:受保护旧双导出的家谱详情、列表和申请列表多为通用 `Object/ListResult`;新线上虽改为 `RListAppGenealogyVo/RAppGenealogyVo/RListGenealogyJoinApplyVo` 等类型化响应,但三个工作区模型均无 `required`。`AppGenealogyVo.genealogyId` 仍是 JSON `integer/int64`,合法最大值在 JavaScript 中不可逆失真;`roleType/status/memberStatus` 无 enum,且没有可直接用于 current context 的 `canView`。首批工作区只要求 `genealogyId/genealogyName/canView/canManage/canEditContent/roleType` 的最小闭包,不要求 22 个字段全部必填。`GET /genealogy/app/genealogies/quota` 虽已返回 `GenealogyQuotaVo`,它属于后续创建/加入写流程,不混入 G01/G05 只读批次。申请、用户、字辈等其他 int64 只在各自实际消费批次逐项关闭,不能用解析后 `String()` 冒充无损。 - G03:任务 35 已完成三人专项审查。受保护旧双导出的 `GenealogyCreateBody` 与线上 `AppGenealogyCreateBody` 都只建谱,页面又缺可信 `regionCode`,通用人物 POST 不能保证唯一始祖或两写原子性。生产目标固定为最终按钮一次 `AppGenealogyBootstrapBody` 创建谱+OWNER+一世始祖+READY,使用 `/genealogy/app/region/search`、`GenealogyRegionCode`、`RegionSelectVo.selectable`、统一 `GenealogyAccessPreset`、词法 ID、Idempotency-Key 与无 PII operation-status;当前后端 `G03-BOOTSTRAP-OPENAPI-CONTRACT BLOCKED` 和客户端 `G03-BOOTSTRAP-CLIENT-RELEASE BLOCKED` 均为独立发布红灯,见 5.12。 - G01/G05 只读工作区:`/mine` 是唯一可访问集合 owner,`/{genealogyId}/overview` 是 G05 唯一读取 owner,不同时调用语义重复的 `/{genealogyId}`。G01 必须在 `onShow` 或失效事件中取消旧请求并成功取得严格列表后才 reconcile;网络/5xx 不写撤权 tombstone。G05 在新请求前清旧数据并取消迟到响应,远端失败不回退 fixture。线上没有当前首屏用于确认来源和可信度的 `source/manager/certification/ancestorName/parentName/branchName/updatedAt/activeCount`;首批可以诚实隐藏或降级这些可选展示,若产品坚持保留则另补后端合同,不能让 fixture 成为真实详情。 - 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` 同用枚举。新合同落地时原子删除旧 DTO、pair、映射和对应旧测试,不保留邀请码 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 为差异真相,在后端澄清批次起点前不得发送本地合并结果。 ### 2.9 新线上 OpenAPI 差异与运行边界 - 新运行基址为 `https://backend-api.ddxcjp.cn`,已经由 `utils/config.js` 唯一持有且无尾斜杠;当前仍保持 `mode: 'mock'`,不会让尚未接完的页面误打真实服务。线上文档的 `servers` 却仍生成 `http://backend-api.ddxcjp.cn`;客户端配置只能使用 HTTPS,后端需修正文档声明,不能让 H5 产生混合内容风险。 - 本地 112 条路径中 109 条仍在线;旧 `/genealogy/app/files/reference`、`/genealogy/app/files/upload`、`/genealogy/pc/files/upload` 三条当前不在线,线上另有 613 条路径。任何上传与文件引用实现都必须先按新线上合同重新审查。 - 线上把本地若干 `Genealogy*Body/View` 改为 `AppGenealogy*Body/Vo`,部分字段说明、响应包装和 operationId 也已漂移;现有离线测试只能证明受保护双导出内部的 G 系列快照,没有证明线上与旧快照一致。 - 行为验证服务已能返回 `validToken`,短信发送体也强制接收它;但密码登录体没有票据字段。A01 密码登录、注册、忘记密码三条流程共用 TAC 是产品硬要求,后端必须明确各自 `sceneCode`、provider/captchaType、票据一次性消费与过期/重放/限流规则,以及密码登录如何强制校验。 - 新线上文档的 `722` 条路径中没有任何 `/genealogy/app/v2/`,`507` 个 schema 中没有 `LineageGraphWindow/LineageOverview/LineageLocator` 或 `schemaVersion/treeVersion/familyUnits/edges/rootVisibility/ancestorPathSegments/affected*Ids` 等辨识字段,`LINEAGE_QUERY_INVALID/LINEAGE_FOCUS_NOT_AVAILABLE/TREE_VERSION_CHANGED/RELATIONSHIP_PATCH_EMPTY` 也全部不存在。`tests/lineage-openapi-contract.ps1` 已对受保护 JSON/YAML 建立聚合红灯并证明四条目标操作和三个固定根模型同时缺失,因此不能解除 `API-T01-001` 门禁,也不能开始任务 12。 - 已验证 `http://localhost:5173` 对 `/captcha/challenge` 的预检允许 `POST`、`content-type`、`clientid` 和 credentials;正式 H5 域名、App 原生请求、错误码与限流仍须分别验证,不能用本次预检替代上线验收。 ### 2.10 认证与 TAC 后端缺口账本 认证客户端批次已经完成,但 `runtimeConfig.mode` 继续固定为 `mock`;下列问题关闭、后端提供同版本双导出并完成真实联调前,客户端不得切换远端或宣称登录注册可上线: - `API-AUTH-TAC-001`:密码登录体尚无票据字段。后端必须让 `POST /genealogy/app/auth/login` 强制消费与短信发送相同安全语义、绑定 `APP_PASSWORD_LOGIN + tenant + client + canonical phone` 的短时单次票据;在此之前 A01 密码登录入口保持不可用,不能仅由客户端先展示滑块。 - `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 创建:相同幂等键返回原结果,并发或不同键重放不能创建第二个任务;跨手机号、场景、租户或客户端全部失败。 - 无障碍不得成为降风控布尔开关:禁止 `accessibility=true`、`skipCaptcha`、检测 TalkBack 后放行、供应商故障时直接发短信,以及由客服绕过验证中心触发短信。P0 先落 provider-neutral 验证中心和支持屏幕阅读器、文字聊天/中继的可审计人工兜底;文字渠道只是通信媒介,各 `sceneCode` 仍须定义独立身份或号码控制证据。案件继承且不得改写原 session 的 subject/scene,具有去重、RBAC、主体/IP/设备/审核员限额、审计、服务时段、容量与 SLA,高风险找回或换号双人复核;坐席只提交决定,验证中心才可签票。异步案件和批准授权可在合理期限内恢复,用户重新进入原流程时才激活短时 `validToken`,避免通知前过期;不得要求用户证明残障。中国大陆非交互风控供应商只进入限时 POC,真实 UniApp Android WebView 必须证明 TalkBack、外接键盘、Switch Access、弱网、错误票据、误杀和攻击拦截门槛,达标后才可成为默认自动路径,不能预先宣称无障碍合规。只有此前在同一 subject 的已认证会话绑定私钥、服务端 nonce、RP/App 绑定、`userVerification=required`、短时单次且检查撤销的设备断言才可独立放行;普通设备指纹、完整性检测或仅 user-presence 只能加权,注册和未绑定设备不能使用。音频验证码必须另经可懂度、听障覆盖和 ASR 对抗 POC,不能单独上线或充当唯一替代。 - 客户端当前只完成浮层壳层的对话语义、焦点进入/圈定/恢复、Escape/Android 返回、原生刷新/关闭、48px 目标与小视口滚动;第三方 TianAi 仍以指针拖动为主,不能据此宣称 TalkBack 可完成。`tests/auth-android-accessibility-release-gate.ps1` 在缺少非拖动等价路径与三人 MuMu 证据时固定输出 `ANDROID-AUTH-ACCESSIBILITY-RELEASE BLOCKED`。 ### 2.11 F 系列线上写合同与上传阻塞 本节只记录 2026-07-22 从 `https://backend-api.ddxcjp.cn/v3/api-docs` 只读核对到的线上事实,不改写受保护的 `APP.openapi.yaml/json`,也不代表 F 页面已经接入写接口: - 动态:`AppFamilyFeedBody.feedContent` 必填且 `minLength=1`,其余字段为 `feedType`、`mediaOssIds`、`sortOrder int64`、`status`。`AppFamilyFeedCommentBody.commentContent` 必填,但文档边界为 `minLength=0/maxLength=1000`,另有可选 `parentCommentId int64`;空字符串虽然被模型允许,产品页仍可采用更严格的非空校验,但适配器不能把页面规则误写成服务端约束。 - 谱文与相册:`AppArticleBody.articleTitle/articleContent` 必填且均为 `minLength=1`,`categoryId/coverOssId/sortOrder` 为 int64;`AppAlbumBody.albumName` 必填且 `minLength=1`,`coverOssId/sortOrder` 为 int64。所有 int64 标识在客户端边界继续以词法字符串保存,只有合同已经消除歧义的请求适配器才可编码。 - 上传初始化:`SysOssResumableInitBo` 的 `uploadId/fileName/fileMd5/totalSize/totalChunks/chunkSize` 六项必填;`fileMd5` 匹配 `^[a-fA-F0-9]{32}$`,三个大小或分片数均要求正整数。初始化响应的即时命中分支 `SysOssResumableInitVo.ossId` 是 int64。 - 分片与完成:chunk 要求 query `uploadId/chunkIndex/chunkMd5` 和 multipart `file`;complete 的 `SysOssResumableCompleteBo` 要求 `uploadId/fileName/fileMd5/totalSize/totalChunks`,MD5 与正整数边界同初始化。 - 硬阻塞:complete 返回的 `SysOssUploadVo.ossId` 被声明为 string,而创建照片的 `AppAlbumPhotoBody.ossId` 必填且声明为 int64;两条成功路径对同一对象存储标识给出冲突类型。后端统一类型或明确无损转换责任并重新导出同版本文档前,客户端不得自行 `Number()`、不得提交照片创建,也不得显示上传成功。F09 因此只能保留明确的本地预览。 ### 2.12 R 系列线上接口与静态迁移边界 三位评审者已经同时从接口字段、业务闭环、异常交互和视觉风险审查 R01—R11。以下是 2026-07-22 线上 OpenAPI 证据与 Task8 静态批次边界,不表示页面已经调用真实接口: - 人物:R01/R02 对应 `/genealogy/app/genealogies/{genealogyId}/lineage/persons` 与详情路径;搜索分页另有 `/page`,接收 `keyword/generation/personStatus` 和必填 `pageQuery`,返回 `TableDataInfoAppLineagePersonVo`。`AppLineagePersonBody` 只要求 `name`,页面旧 `role/legacy` 不是可靠请求字段;人物状态与写权限也没有正式字典或能力位。Task8 只复用树成员只读 owner,真实人物写入留到独立接口批次与 T01 v2 原子变更一并治理。 - 人情往来:R03/R04 对应 `relative-records`,不是强制依赖 `ceremonyId` 的 ceremony gifts。请求只要求 `relativeName`,另有 `relationName/eventName/eventTime/giftAmount/recordContent/mediaOssIds`;模型没有收礼/送礼方向及金额币种语义,后端补齐或产品明确单向定义前不能声称完整礼账闭环。 - 礼仪:R05—R07 对应 `ceremonies`,请求要求 `ceremonyTitle/ceremonyType`;`ceremonyType/status` 无正式枚举。邀请列表只有 `inviteeUserId/inviteStatus` 等字段,页面若展示姓名必须与同谱成员选项按用户 ID 受控联接,且“受邀人”不能直接写成“已参与者”。 - 成长:R08 对应 `growth-records`,请求要求 `recordTitle`;页面必须额外强制 `lineagePersonId`,因为家谱级列表没有人物筛选参数。`recordType/status` 无枚举,不能据此在客户端发明分类。 - 人生事:R09 没有独立线上端点,且 `growth-records.recordType` 没有枚举或人生事件值说明。后端提供正式合同前页面硬关闭,不读取、不写入、不展示 fixture 时间轴。 - 备忘:R10 对应 `memos`,请求要求 `memoTitle`;`completed/status` 是无枚举字符串,也没有独立幂等切换端点或版本字段,Task8 禁止点击卡片本地翻转官方状态。 - 功德:R11 对应 `merit-records`,请求要求 `donorName/meritTitle`;`meritType/status` 无枚举,`amount` 也没有币种、精度或非负边界。当前汇总只能来自只读列表,新增预览不得改变正式次数或金额。 - 公共边界:上述接口都只说明“需要登录”,响应没有统一 `canCreate/canEdit/canDelete`;任何页面角色、创建人或 fixture 权限都不能冒充服务端授权。所有 int64 ID 保持词法字符串;未知实体、缺参、跨谱必须失败关闭。真实写接口未接入前,R 页只允许独立且明确未提交的本地预览,生产路由没有结果能力。 ### 2.13 N/M 系列线上接口与静态迁移边界 三位评审者已同时核对页面流程、2026-07-22 线上 OpenAPI、异常交互和视觉风险。Task9 完成 N/M 安全导航与诚实静态边界;其后只有 M07 在独立 Task25 接入已核对的真实反馈 owner,其余页面仍未提前接入远端: - 消息:Task29 把 `GET /genealogy/app/notifications` 与 unread-count 固定为独立读取批次:无筛选列表完整返回当前账号最多 200 条活动通知、最新优先,计数精确覆盖同一集合;首版 adapter 只公开 `snapshotKey/title/content/publishedAt/unread`,N02 由当前内存 generation+ordinal key 读取完整正文。Task30 单独约束两个已读 POST 的字符串 ID、幂等与 read-all 截止点。当前受保护双导出缺 unread-count 和专用模型,线上又无 required/enum/容量、ID 为 int64 且匿名行为与文档冲突,因此两项门禁均为红灯,见 5.6/5.7。首批删除所有通用目标 CTA;后端没有闭合 `bizType` 目标字典前,客户端不猜路由且永不执行服务端 URL。 - 个人资料: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 精确空串清空,`profileVersion+If-Match+409` 防并发覆盖,当前 `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 session;unknown 也清本机回 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。 - 帮助与反馈:M06 首批固定 `GET /genealogy/app/help-articles` 为完整列表唯一 owner,不调用详情、不消费 `helpId`;adapter 只允许投影分类、标题和纯文本正文,分类由当前列表动态派生。受保护双导出仍是通用 `ListResult/RList`,线上专用模型又缺 required、正文格式、仅发布和顺序语义,匿名行为也与文档 401 冲突,因此 `HELP-CENTER-OPENAPI-CONTRACT BLOCKED` 保持红灯,详见 5.4。`AppFeedbackBody.feedbackContent` 必填,`feedbackType/contactInfo` 可选且无 enum;M07 已由 `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。 ## 三、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 | | 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.3;Task29 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 空态或添加家谱弹层 | 进入 G05、G08、G09;已加入或我创建时回 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 | | 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 审查 | | T05 | 编辑成员 | `pages/tree/t05-edit-member` | 修改指定成员身份和生平资料 | T03 编辑入口 | 当前本地预览确认放弃后用 `goBack()` 回原 T03 实例且不产生结果;真实写成功后才刷新当前活动成员 | 加载、字段校验、长简介、保存中、本地预览、失败、无权限、放弃确认 | 成员档案与权限 | 待对应业务阶段 OpenAPI 审查 | | 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 已核对;线上有动态列表/写入能力,当前静态批次只读且按家谱隔离 | | 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 | | 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 回 F04,edit 回精确 F05;预览不保存、不产出结果 | 新建、编辑、校验、加载、本地预览、失败保留、长正文、放弃确认 | 谱文编辑 | 线上 POST/PUT 已存在,`categoryId/coverOssId` 为 int64;真实调用未在导航批次启用,详见 2.11 | | F07 | 相册列表 | `pages/family/f07-album-list` | 按家谱浏览相册并制作独立的新相册预览 | F01 相册入口 | 正式相册进入 F08;本地预览不插入列表;返回同一 F01 | 加载、列表、长列表、空、失败、创建弹窗、本地预览、校验、权限、放弃确认 | 相册 | 线上创建要求 `albumName`;当前不调用写接口、不伪增列表或计数 | | F08 | 相册详情 | `pages/family/f08-album-detail` | 按 `genealogyId + albumId` 浏览照片墙和相册信息 | F07 相册卡 | 先关闭照片预览,再返回 F07;进入同一相册的 F09 | 加载、照片墙、末张预览、Android 返回先关预览、空相册、相册失效、失败、权限 | 相册与媒体 | 任务 7 已核对;当前严格按复合身份读快照,未知或跨谱相册不回退首条 | | F09 | 上传照片 | `pages/family/f09-media-upload` | 选择照片并填写批量及逐张说明的本地预览 | F08 添加照片入口 | 预览不上传、不生成成功态;取消确认放弃后回同一 F08 | 初始、权限、最多九张、增删、独立说明、必填、本地预览、放弃确认、无效相册 | 媒体上传 | 旧 `/files/upload` 已下线;resumable init/chunk/complete 的 complete 返回 `ossId` 为 string,而照片创建要求 int64;后端消除类型冲突前不得转换或提交,详见 2.11 | | F10 | 家族视频 | `pages/family/f10-video-list` | 按有效家谱说明当前视频服务尚未开放 | F01 视频入口 | 当前只返回同一 F01 | 待开放、无效家谱、返回 F01;未来能力不冒充当前功能 | 视频服务 | 任务 7 未发现可支撑当前页面闭环的已启用视频产品合同,保持关闭 | | R01 | 人物录 | `pages/records/r01-people-list` | 按当前家谱搜索和浏览人物只读快照 | F01 人物录入口 | 进入带精确家谱与模式的 R02;返回 F01 | 加载、列表、搜索、无结果、空、失败、分页、跨谱失败 | 人物记录 | 任务 8 已核对 `lineage/persons/page`;ID 必须保持词法字符串,真实分页尚未接入 | | R02 | 人物详情 | `pages/records/r02-person-detail` | 查看人物或制作不写库的新建/编辑预览 | R01 人物卡或预览入口 | 预览不产出结果;进入 R08 或硬关闭的 R09;取消回来源 | 查看、新建、编辑、校验、预览、隐私、失效、跨谱 | 人物记录与权限 | 任务 8 已核对 `AppLineagePersonBody`,`name` 必填;真实写入未启用,权限字典待补 | | R03 | 贺礼簿 | `pages/records/r03-gift-list` | 浏览同谱人情往来只读快照 | F01 贺礼簿入口 | 进入携带 `relativeId` 的 R04;返回 F01 | 加载、列表、空、失败、跨谱 | 人情往来 | 任务 8 已核对 `relative-records`,不是 ceremony gifts;收礼/送礼方向语义仍缺 | | R04 | 贺礼编辑 | `pages/records/r04-gift-editor` | 查看往来记录或制作不写库的本地预览 | R03 记录或预览入口 | 预览不插入列表、不删除记录;取消回来源 | 查看、新建、编辑、校验、本地预览、无效实体、跨谱 | 人情往来与权限 | 任务 8 已核对 `relativeId` 与 `relativeName` 必填;真实写入未启用,只允许本地预览 | | R05 | 礼仪列表 | `pages/records/r05-ritual-list` | 浏览同谱礼仪活动只读快照 | F01 礼仪入口 | 进入 R06 或 R07 预览;返回 F01 | 加载、列表、空、失败、活动展示、跨谱 | 礼仪活动 | 任务 8 已核对 ceremonies,`ceremonyTitle/ceremonyType` 必填且类型、状态无正式枚举 | | R06 | 礼仪详情 | `pages/records/r06-ritual-detail` | 查看精确礼仪与受邀人快照 | R05 活动卡 | 返回 R05;进入同一礼仪 R07 | 加载、详情、受邀人、失败、失效、跨谱 | 礼仪活动与邀请 | 任务 8 已核对 invitations;姓名需与成员选项受控联接,不能把受邀者冒充参与者 | | R07 | 礼仪编辑 | `pages/records/r07-ritual-editor` | 制作新建或编辑礼仪的本地预览 | R05 预览入口或 R06 编辑入口 | create 预览回 R05,edit 预览回原 R06;不产出结果 | 新建、编辑、校验、本地预览、无效实体、跨谱 | 礼仪活动与权限 | 任务 8 已核对 `ceremonyId`;必填与枚举见 R05,真实写入未启用 | | R08 | 成长日志 | `pages/records/r08-growth-journal` | 按同谱人物展示成长快照并制作独立预览 | R02 或 T03 人物入口 | 预览不插入正式时间轴;返回实际人物来源 | 加载、时间轴、空、失败、预览弹窗、必填、跨谱 | 人物成长记录 | 任务 8 已核对 growth-records;客户端必须强制 `lineagePersonId`,`recordType` 无枚举 | | R09 | 人生事 | `pages/records/r09-life-events` | 明确说明人生事件服务当前不可用 | R02 或 T03 人物入口 | 不读取或写入成长记录;安全返回人物来源 | 接口缺失、无效人物、返回来源 | 人生事件 | 任务 8 已核对:没有独立人生事件接口,后端补端点或正式类型字典前硬关闭 | | R10 | 家族备忘 | `pages/records/r10-memo-list` | 浏览同谱备忘快照并制作独立预览 | F01 备忘入口 | 预览不插入列表、不切换正式完成状态;返回 F01 | 加载、列表、空、失败、预览、跨谱 | 家族备忘 | 任务 8 已核对 memos,`memoTitle` 必填而 `completed` 无枚举,真实切换未启用 | | R11 | 功德记录 | `pages/records/r11-merit-records` | 浏览只读汇总并制作独立贡献预览 | F01 功德录入口 | 预览不改变正式汇总或列表;返回 F01 | 加载、汇总、列表、空、失败、预览、跨谱 | 功德与贡献 | 任务 8 已核对 merit-records,`donorName/meritTitle` 必填,类型、状态与金额边界无枚举 | | N01 | 消息中心 | `pages/notification/n01-message-center` | 展示当前账号完整活动通知快照与服务端读状态,不猜业务目标 | G01 或 M01 消息入口 | 以当前内存 `snapshotKey` 进入 N02;返回来源 | 加载、未读、已读、空、失败重试、认证失效、长内容、并发刷新 | 消息与通知 | Task29 读取红灯已建立;完整活动集合、专用 required、纯文本、时区、二值状态和计数同域待关闭,见 2.13/5.6;Task30 前不得本地伪写 | | N02 | 消息详情 | `pages/notification/n02-message-detail` | 从当前 generation 的内存快照展示完整纯文本正文,不持有服务端 ID | N01 消息卡 | 返回 N01;无快照时提示从消息中心重新打开,不跳业务页 | 加载、详情、无快照、认证失效、长正文、读状态写入待开放 | 消息与通知状态 | Task29 选定 list-owned snapshot 且删除目标 CTA;Task30 独立约束私有字符串 ID 与幂等写入,见 2.13/5.6/5.7 | | M01 | 我的 | `pages/profile/m01-profile-home` | 展示脱敏账号身份并保持通知、服务与设置入口可达 | 根 Tab | 进入 M02、M03、M06、M08、M09、M10 或 N01 | 身份与通知局部加载、正常、失败重试、认证失效、未读数不可用、服务可用性 | 个人中心聚合 | Task28 profile GET 红灯保证身份失败不锁菜单,见 2.13/5.5;Task29 unread-count 红灯删除 fixture 伪数并统一“未读消息”,见 2.13/5.6 | | M02 | 个人资料 | `pages/profile/m02-edit-profile` | 从唯一 profile owner 初始化并以版本化 dirty-only merge 保存昵称、真实姓名和邮箱 | M01 资料入口 | 成功应用权威响应并留在本页;放弃确认后回 M01 | 加载、失败重试、认证失效、异步 baseline、字段校验、保存、结果未知、版本冲突、账号切换、头像未接入 | 用户资料 | Task28 读取红灯仍是前置;Task31 已建立 PUT merge、清空、If-Match、typed response 和 409 红灯,头像不混入,见 2.13/5.5/5.8 | | M03 | 账号与安全 | `pages/profile/m03-security-settings` | 展示密码入口与脱敏绑定手机号,不伪造设备安全结论 | M01 安全入口 | 进入 M04 或 M05;返回 M01 | 手机号局部加载、正常、失败重试、认证失效、功能受限 | 账号安全 | Task28 已建立 profile GET 红灯;普通读取失败不得阻断密码入口,设备状态仍无合同,见 2.13/5.5 | | M04 | 修改密码 | `pages/profile/m04-change-password` | 以当前密码重新认证并安全更新统一密码凭证;门禁前保持本地预览 | M03 密码入口 | 确定错误留页;200/401/409/结果未知清本机并回 A01;放弃确认回 M03 | 空字段、15/64 边界、Unicode/NFC、blocklist、当前错误、并发、限流、提交中、结果未知、进程终止 | 账号安全与会话 | Task33 已建立 raw writeOnly、ALL 会话撤销、typed 错误与 session marker 红灯;四条密码 wire 必须同批迁移,见 2.13/5.10 | | 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 | | 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 | ## 四、封存与已移除页面 | 页面 | 当前决定 | 合同边界 | | --- | --- | --- | | A02 账号登录 | 已完整并入 A01,不保留兼容路由 | 任何注册、重设或退出后的登录目标统一指向 A01 | | A03 | 已移除 | 不恢复无明确业务职责的历史入口 | | A06 登录状态 | 源码保留、活动路由封存 | 未来只有账号冻结、停用或风险限制等无法继续登录的阻断状态,且产品重新确认独立页面后才能恢复 | | G02 | 并入 G01 空状态 | 搜索、邀请码加入和创建三个入口由 G01 空状态承载 | | G04 | 并入 G03 始祖步骤 | 创建家谱与录入始祖属于同一可恢复流程 | | G07 | 并入 G06 | 搜索、筛选、结果与无结果均由 G06 双模式承载 | | T02 | 并入 T01 | 世系阅读提示、空态和失败态不再拆分独立路由 | ## 五、后续接口审查填充规则 每个对应业务阶段都必须先解析并比对两份 OpenAPI 导出,再按三人独立首审、交叉补漏、反向质询和共同收敛的顺序更新本文件。每个页面和用户操作都要补齐以下事实: 1. 接口分类:App 可直接使用、PC 专用、App/PC 可能共用待确认、App 合同不完整、App 缺失、页面无合理用途或双方需重定义。 2. 精确合同:路径、方法、鉴权、请求参数、字段类型、必填性、可空性、枚举、响应模型和错误码。 3. 数据行为:分页、排序、筛选、上传、幂等、防重复提交、并发冲突、权限和数据副作用。 4. 页面结果:成功、失败、取消、返回、完成、来源页刷新、重复进入和目标数据过期时的表现。 5. 证据与状态:OpenAPI 位置、页面与代码位置、MuMu 操作步骤、三人结论、后端问题编号和关闭条件。 接口问题不能只写“缺接口”或“字段不够”。需要给后端的每一项都必须能够直接用于修改 Apifox,并明确验收步骤。后端更新后由用户重新导出 JSON 和 YAML,先通过语义一致性与差异合同,再允许页面接入。 ### 5.1 已核对结论 API-A04-001 - 页面与动作:A04 提交注册并建立会话。 - 当前接口:`POST /genealogy/app/auth/register`。 - 已确认响应:受保护旧快照的响应形状已经过期;2026-07-22 新线上注册成功响应为 `RAppLoginVo`,`data` 引用 `AppLoginVo`,唯一会话字段为 `access_token`。密码登录与短信登录使用同一响应链。 - 客户端合同:`utils/api.js` 只消费 `AppLoginVo.access_token`;旧字段读取已经删除。必须等同版本双导出落地后再把页面接到远端,不能把线上证据手工写回受保护源文件。 - 产品结论:取得并保存有效令牌后直接清理认证流程并进入 G01;不保留“注册成功后再登录”的并行终点。 - 尚未关闭范围:短信发送、行为验证、限流和验证码状态机不由本结论代替,按后续短信阶段单独审查。 ### 5.2 后端问题单 API-T01-001 **优先级:** P0;现有 6 人默认数据已能出现确定性断线,当前合同也无法安全支持几十代、几百代。 **受影响页面:** T01 主页面,T03 定位,T04 新增亲属,T06 编辑关系,T07 搜索成员;G01/G05 只受入口与焦点参数影响。 **当前接口与问题:** - `GET /genealogy/app/genealogies/{genealogyId}/lineage/tree` 只接受 `genealogyId`,返回递归 `LineagePersonTreeView[]`。 - 递归 `children/spouses` 与 `fatherId/motherId` 不能无歧义表达多个家庭联合点、单亲、收养、继亲、监护、主入边、窗口边界和树版本。 - 世系 ID 使用 `integer/int64`,超过 JavaScript 安全整数时会改变身份。 - 缺少世代×支系概览、人物定位、关系编辑和统一树版本并发合同。 **要求后端在 Apifox 原子更新:** ```http GET /genealogy/app/v2/genealogies/{genealogyId}/lineage/tree GET /genealogy/app/v2/genealogies/{genealogyId}/lineage/tree/overview GET /genealogy/app/v2/genealogies/{genealogyId}/lineage/persons/{personId}/locator PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relationshipId} ``` - `/lineage/tree` 查询参数固定为 `mode,focusPersonId,ancestorDepth,descendantDepth,boundaryId,cursor,limit,treeVersion`,不接受 `branchId/generation`。FOCUS 模式可省略焦点,但非空焦点只能是 VISIBLE 稳定人物 ID;REDACTED、不可见、过期或不存在的焦点统一返回 `404 LINEAGE_FOCUS_NOT_AVAILABLE`。深度为 0—20 的整数且默认上二代/下二代;BOUNDARY 模式必须同时提供 `boundaryId/cursor/treeVersion` 且不得带焦点或深度;`limit` 为最小 1、默认 200、最大 500,非法组合返回 `422 LINEAGE_QUERY_INVALID`。 - 响应唯一根模型:`LineageGraphWindow`,必含 `version,state,genealogyId,nodes,familyUnits,edges,window`;版本字段固定为 `schemaVersion,treeVersion,generatedAt`,`state` 以 `EMPTY/POPULATED` 为 discriminator 使用 `oneOf`。EMPTY 精确要求空 nodes/familyUnits/edges、空 `entryPersonIds`、空 boundaries、`focusPersonId=null、generationRange=null、returnedNodeCount=0`;POPULATED 要求非空 nodes、引用其中 VISIBLE 节点的焦点、有效入口、非空代际范围,并满足 `returnedNodeCount === nodes.length`。 - `entryPersonIds` 精确等于当前窗口无 primary 入边的节点集合,secondary 入边不取消入口身份;入口人物 `entryReason` 为 `GENEALOGY_ROOT/WINDOW_CUT/DISCONNECTED_COMPONENT` 且恰有零条 primary 入边,非入口 `entryReason=null` 且恰有一条 primary 入边。全谱根只由 overview 的可见根/隐私根计数与 locator 的根可见性分支表达;旧 `rootPersonIds/rootReason` 不再属于窗口合同。 - Node 以 `visibility` 为 discriminator 使用 `oneOf`。VISIBLE 精确包含可见身份字段,稳定人物 ID 不得以 `redacted:` 开头,`sex` 固定为 `MALE/FEMALE/UNKNOWN`;REDACTED 只允许 `id,generation,displayName,order,visibility,entryReason`,名称固定为“隐私成员”,opaque ID 精确使用 `redacted:{treeVersion}:{token}` 并仅在该版本内作当前图内部引用,不得进入 FOCUS、locator、搜索、写接口或任何 `focusPersonId/targetPersonId`,也不得泄漏头像、性别或支系字段。 - `FamilyUnit` 必含 `id,anchorPersonId,partnerRelationship,partners,order`;一个单亲成员或一对伴侣组成一个家庭单元。`partnerRole` 只允许 `ANCHOR/PARTNER`;双人关系对象包含稳定 `relationshipId、relationshipKind=PARTNER、relationType、status`,伴侣 `relationType` 为 `MARRIAGE/PARTNERSHIP/UNKNOWN`、`status` 为 `ACTIVE/ENDED/UNKNOWN`,单亲为 `null`,多配偶拆为不同家庭单元。 - `ParentChildEdge` 必含 `id,familyUnitId,childId,lineageParentId,parentRelations,primary,order`;每项父母关系包含稳定 `relationshipId,relationshipKind=PARENT_CHILD,personId,parentRole,relationType`。`parentRole` 为 `FATHER/MOTHER/PARENT/GUARDIAN/UNKNOWN`,`relationType` 为 `BIOLOGICAL/ADOPTIVE/STEP/GUARDIAN/UNKNOWN`;所有父子关系都检查循环,不能只校验 primary。 - boundary 必含稳定 ID、锚点、方向、原因、隐藏数量和 cursor;WINDOW 锚点的 `anchorId=null`,其他锚点引用对应实体;`hiddenCount` 为非负整数或 `null`,cursor 只在 UNLOADED 时非空。窗口内匿名人用 `Node.visibility=REDACTED`,窗口外隐藏拓扑用 boundary REDACTED,同一对象不得重复表达;运行时网络 FAILED 不写进后端枚举。 - 所有实体、关系和引用 ID 使用非空字符串;`avatarOssId` 只允许非空字符串或 `null`。 - cursor 绑定 `genealogyId/treeVersion/boundaryId`;版本变化返回 HTTP `409` 和 `TREE_VERSION_CHANGED`。 - overview 只接受必填 `treeVersion`,版本变化返回 `409 TREE_VERSION_CHANGED`;响应以 `state=EMPTY/POPULATED` 使用 `oneOf`。EMPTY 精确为 `genealogyPersonCount=0、genealogyRootPersonIds=[]、redactedGenealogyRootCount=0、generationRange=null、buckets=[]`;POPULATED 要求正数总量、非空范围和 buckets,并满足可见根数加隐私根数至少为 1、全部 bucket 三类计数之和等于总量。可见根与 bucket `focusPersonId` 只能使用 VISIBLE 稳定 ID,隐私根只计数不返回 opaque ID。locator 将 `rootVisibility=VISIBLE/REDACTED` 与 `pathCompleteness=COMPLETE/REDACTED_GAPS` 独立建模;`ancestorPathSegments` 用 VISIBLE 人物 ID 段与不含 ID 的 REDACTED gap 段表达任意中间隐私,支持可见根但中间祖先隐藏,任何路径都不得包含 opaque ID。 - 世系写接口携带 `If-Match`,成功返回新 `treeVersion` 以及受影响人员、家庭和关系 ID;关系 PATCH 以不可变 `relationshipKind` 为 discriminator 使用 `oneOf`,PARTNER 只更新 `relationType/status`,PARENT_CHILD 只更新 `relationType/parentRole`。每个分支至少提交一个可修改字段,省略字段保持原值;只有 `relationshipKind` 的空更新返回 `422 RELATIONSHIP_PATCH_EMPTY`,不得偷换参与人。 - 客户端 Scene 根固定为 `{ sceneVersion, treeVersion, focusPersonId, bounds, items }`;`utils/lineage/scene.js` 唯一生成 `sceneVersion`,缺失版本或相同版本对应不同 payload 均拒绝原子替换。瞬时 `selectedId` 不进入 Scene 或版本摘要,renderjs 只用它在同一 Canvas 动态重绘光晕。 - 四条操作统一声明 `200/400/401/403/404/422/429/5XX`;tree、overview、relationship PATCH 另声明 `409`。错误响应根层唯一业务码字段为必填字符串 `businessCode`,稳定码必须在对应响应 `oneOf` 分支中用单值 enum(OpenAPI 3.1 可用 `const`)表达;关键词、description、example 和无关 metadata 都不算证明。非空 `generationRange` 固定为关闭额外字段的 `{ minGeneration, maxGeneration }`,两项均为大于等于 1 的整数,大小顺序交给运行时 validator。 - 旧 v1 树路径保持原合同;App 只实现上述四条固定 `/genealogy/app/v2/...` 路径,不双读、不运行时探测版本。 **关闭条件:** 用户从更新后的 Apifox 重新导出 JSON/YAML;两份文件同时通过 `tests/lineage-openapi-contract.ps1`;三人逐字段复核后,客户端才能开始规范化、布局和 Canvas 实施。 ### 5.3 后端问题单 API-GENEALOGY-WORKSPACE-001—003 **优先级:** P1;阻塞 G01/G05 切到 remote 和正式上线,不阻塞继续审查无依赖业务域。 **唯一 owner:** `GET /genealogy/app/genealogies/mine` 持有当前账号可访问集合;`GET /genealogy/app/genealogies/{genealogyId}/overview` 持有 G05 只读展示。首批不接语义重复的 `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/401,401 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-002:可访问集合与能力投影。** `/mine` 中只有 `canView=true` 的行能进入 current context;也接受后端明确并由集成测试证明“只返回当前账号仍可查看 ACTIVE 家谱”的等价合同。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 权限。 **展示取舍:** 当前 G05 fixture 还显示 `source/manager/certification/ancestorName/parentName/branchName/updatedAt/activeCount`,线上没有等价字段。首批允许把这些区域隐藏或使用明确“待补充/待同步”的非业务降级;若产品要求继续把它们作为可信首屏信息,后端需另补字段或明确组合接口。不能为了复刻 mock 把 22 个字段全部列为当前硬门禁,也不能把 mock 值带进 remote。 **关闭条件:** 后端从同一版本重新导出 JSON/YAML,`tests/genealogy-workspace-openapi-contract.ps1` 通过;三人复核 enum 与页面消费后,才实现专属 adapter、G01 `onShow` 刷新和 G05 `/overview` 接线。随后完成有效账号行为矩阵和 MuMu 原生状态矩阵;缺少任一层证据都不能把工作区称为可上线。 ### 5.4 后端问题单 API-M06-001—003 **优先级:** P1;阻塞 M06 切到 remote 和帮助内容上线,不阻塞继续审查其他无依赖页面。 **唯一 owner:** 当前 M06 只使用 `GET /genealogy/app/help-articles`。线上 `HelpArticleVo` 已携带完整 `helpContent`,所以手风琴展开直接使用同一列表快照;不调用 `GET /{helpId}`,不建立文章深链、详情缓存或第二正文 owner,也不让 `helpId` 进入页面模型。 **当前线上证据:** 列表返回 `RListHelpArticleVo`,列表项含 `helpId/helpCategory/helpTitle/helpContent/coverOssId/sortOrder/viewCount/status/remark`,但 wrapper 与 VO 都无 `required`,正文无格式语义,分类 query 无正式值域。受保护双导出更旧,只引用通用 `ListResult/RList`,没有专用 Help schema。匿名调用列表、带任意分类列表和详情均为 HTTP 200、`application/json;charset=UTF-8`、`{code:401,msg,data:null}`;线上文档则声明 HTTP 401 string 且 operation 无有效 security。 **API-M06-001:专用响应与最小 schema 闭包。** 列表 200 固定引用 `RListHelpArticleVo`;envelope 的 `code/data` required,`code` 为整数,`data` 为允许空数组的 `HelpArticleVo[]`。每行只把 `helpCategory/helpTitle/helpContent` 设为 required、`minLength >= 1` 的字符串;不要求当前页面不消费的 ID、封面、浏览量、状态和排序字段。后端必须从同一版本重新导出 JSON/YAML,不允许客户端手工补 schema。 **API-M06-002:展示标签、正文格式与发布范围。** `helpCategory` 是去边界空白后可直接展示的标签,不是需要客户端猜字典的内部代码;“全部”只由客户端拥有。`helpContent` 首版明确为 plain text,客户端只按字面显示,不解释 HTML、Markdown、图片或外链。用户侧列表只返回当前可展示的已发布文章,响应数组顺序就是页面展示顺序。若未来需要富文本、文章深链或详情,则另立内容安全和词法字符串 `helpId` 合同,不能偷偷扩张当前批次。 **API-M06-003:认证和错误承载一致性。** 后端可选择规范 HTTP 401,也可继续 HTTP 200+稳定业务 `code=401`,但 SaToken/security、JSON 媒体、OpenAPI 响应、部署行为和客户端 validator 必须一致。有效/失效令牌、空列表、畸形列表、5xx、超时和取消都要有集成反例;失败不得被解释成服务端空列表,也不得回退本地 FAQ 冒充线上成功。 **客户端关闭后的唯一形状:** adapter allowlist 输出 `{renderKey,category,title,content}`,其中 key 只由当前 response generation 与映射前 ordinal 组成;输入即使含 unsafe 或重复 `helpId` 也必须完全丢弃。筛选作用于已映射数组,搜索/分类/刷新前清空展开,旧 generation 迟到响应不得替换新列表。页面必须区分加载、服务端空、搜索无结果、错误重试和认证失效,并用原生按钮、`aria-pressed/aria-expanded/aria-controls`、状态播报及至少 44dp 目标完成无障碍闭环。 **关闭条件:** 后端同版本 JSON/YAML 通过 `tests/help-center-openapi-contract.ps1`;三人复核后才写专属 adapter 和页面异步状态。任务 23 关闭认证门禁后,完成有效账号部署矩阵及 MuMu 的系统字号、TalkBack、焦点、长正文、Android 返回和 M06→M07 验收。详情端点不属于本关闭条件。 ### 5.5 后端问题单 API-PROFILE-READ-001—003 **优先级:** P1;阻塞 M01/M02/M03 使用真实资料和正式 remote 发布,不阻塞继续审查通知等其他只读域。 **唯一 owner:** `GET /genealogy/app/auth/profile` 持有当前登录账号资料。M01 身份卡、M02 表单初值和 M03 绑定手机号行都调用同一个窄 adapter,但不建立跨账号缓存、不通过路由传 PII。M05 当前手机号在 remote 发布前也必须消费同一脱敏结果或隐藏;这不等于提前接入换绑写接口。 **当前线上证据:** 200 已返回 `RAppProfileVo → AppProfileVo`,实体含 `userId/tenantId/userNo/phone/nickName/realName/avatar/sex/birthday/email/registerSource/loginIp/loginDate/status/clientKey/deviceType`,但 wrapper 与 VO 无 required,phone 无 pattern,姓名/邮箱无 length/format。受保护双导出仍为通用 `ObjectResult/RObject`。匿名 GET 实测 HTTP 200、`application/json;charset=UTF-8`、`{code:401,msg,data:null}`;线上文档则声明 HTTP 401 string,operation 只有“需要登录”文字而无有效 security/clientid。 **API-PROFILE-READ-001:专用响应与最小字段闭包。** 200 固定引用 `RAppProfileVo`,envelope 的 `code/data` required,`data` 引用 `AppProfileVo`。实体只 required `phone`,其值必须匹配 canonical `^1[3-9]\d{9}$`。`nickName/realName/email` 都是声明过的可选属性;出现时必须为非空且去边界空白的字符串,姓名长度 1—30,邮箱长度 1—100 且 `format: email`。不要求当前页面不消费的 ID、头像、状态、设备和登录审计字段。 **API-PROFILE-READ-002:可选值与隐私投影。** 三项可选字段唯一未设置形态是属性省略,不再并行接受 null、空串和缺失。adapter 固定输出 `{maskedPhone,phoneAccessibleLabel,nickName,realName,email}`,真正缺席的可选值规范为内部空串;出现但非法则整份失败。明文手机号只在函数局部校验后立即变为掩码和“绑定手机号,尾号 xxxx”读屏标签,不得进入页面模型、缓存、路由、日志或错误。`userId/avatar` 即使是 unsafe int64 也通过 allowlist 完全丢弃,不做 `String(number)`。 **API-PROFILE-READ-003:认证、配置与错误一致性。** 后端可选择规范 HTTP 401 或 HTTP 200+稳定业务 401,但 required clientid、SaToken/security、JSON 媒体、OpenAPI 与部署必须一致。客户端运行模式经 `resolveRuntimeMode()` 校验,错误 remote 配置不得静默回 fixture;读取使用严格 envelope、15 秒超时、取消和 generation 防迟到。账号失效交给 session owner,network/timeout/5xx/畸形数据是可重试读取失败,不存在写请求 uncertain。 **页面与后续写边界:** M01 资料失败只替换身份卡,菜单和底栏保持;查询参数假错误、fixture“创建者”和 remote 下伪通知数删除。M02 异步填表后才建立 baseline,GET 不证明 PUT;未来写批次必须验证省略字段保持、dirty-only payload、清空语义和并发。M03 仅手机号行局部失败,密码入口保持。头像、M04/M05 写入和设备管理不混入本批。 **关闭条件:** 后端从同一版本重新导出 JSON/YAML 并通过 `tests/profile-openapi-contract.ps1`;三人复核后才实现唯一 normalizer、三页局部状态与 M05 脱敏展示迁移。随后以不同资料完整度账号验证掩码、401、畸形响应、5xx、超时、账号切换和取消,并在 MuMu 完成系统字号、TalkBack、焦点、键盘、长昵称和返回流程。 ### 5.6 后端问题单 API-NOTIFICATION-READ-001—003 **优先级:** P1;阻塞 N01/N02、M01/G01 未读数使用真实通知和正式 remote 发布,不阻塞继续审查其他业务域。 **唯一 owner:** `GET /genealogy/app/notifications` 不带 `readStatus` 时持有当前账号完整活动通知集合;`GET /genealogy/app/notifications/unread-count` 持有同一集合的未读数量。当前没有详情端点,N02 只消费列表成功响应形成的不可变内存快照,不建立第二正文 owner。 **当前线上证据:** 线上列表为 `RListNotificationVo → NotificationVo[]`,未读数为 `RLong`;两者和实体都无 required。`NotificationVo` 的 `notificationId/genealogyId/senderUserId/bizId` 是 int64,`readStatus` 无 enum,标题/正文无长度与格式,列表无分页、容量、完整性和排序。受保护双导出列表仍引用通用 `ListResult/RList` 且完全没有 unread-count。匿名 list/count 均实测 HTTP 200、`application/json;charset=UTF-8`、`{code:401,msg,data:null}`,而线上文档声明 HTTP 401 string,operation 无有效 security/clientid。 **API-NOTIFICATION-READ-001:专用响应与最小字段闭包。** 列表 200 固定 `RListNotificationVo`,未读数 200 固定 `RNotificationUnreadCount`;两层 `code/data` required,`code` 为 integer。列表 data 是允许为空且 `maxItems` 不超过 200 的 `NotificationVo[]`;实体 required `noticeTitle/noticeContent/publishTime/readStatus`。标题为 1—50 字符;正文为 1—1000 字符、完整未截断 plain text;时间是带 `Z` 或显式 offset 的 RFC3339 date-time;状态只允许 `READ/UNREAD`。计数是 0—200 的 int32。 **API-NOTIFICATION-READ-002:完整活动集合与快照。** 服务端活动集合本身最多 200 条;列表无筛选时完整返回该集合并按最新优先,未读数精确等于同一集合中 `readStatus=UNREAD` 的数量。两个请求之间并发变化允许瞬时差异,不要求客户端强行相等。adapter 公开 `{snapshotKey,title,content,publishedAt,unread}`,key 为成功响应 generation+映射前 ordinal;筛选不重编号。成功刷新原子替换快照,退出/账号切换清空且不落盘;N02 无 key 时提示“请返回消息中心重新打开”。 **API-NOTIFICATION-READ-003:认证、错误与内容安全。** 后端统一 required clientid、SaToken/security、JSON 媒体、HTTP 401 或业务 401 的文档与部署行为。客户端拒绝无时区时间、未知状态、空/超长标题正文和畸形 envelope;失败不得回退 fixture。首批丢弃所有 ID、sender、`noticeType/bizType/bizId`,不解释 HTML/Markdown/URL,不执行目标跳转;未知业务通知仍完整显示内容。 **客户端关闭后的唯一行为:** N01 摘要最多 160 个 Unicode 字素并可换行,N02 显示同一快照完整正文;M01/G01 共同调用 count owner,文案为“未读消息”,可见 `99+` 但读屏播报真实数。读取批次原子删除 fixture 未读数、本地已读 mutation、通用 G10/审核 CTA、N02 目标按钮和假重试;写能力等待 5.7。 **关闭条件:** 后端同版本 JSON/YAML 通过 `tests/notification-read-openapi-contract.ps1`;三人复核后才实现读取 adapter 和四页局部状态。认证门禁关闭后完成 0/1/99/100/200、并发、畸形响应和账号切换反例,再在 MuMu 验证系统字号、TalkBack、键盘/焦点、长文本、刷新、N01→N02 和 Android 返回。 ### 5.7 后端问题单 API-NOTIFICATION-STATE-001—003 **优先级:** P1;阻塞真实单条/全部已读和四页状态收敛。必须在 5.6 读取批次之后独立实施,不能与读取代码混成一个不可验证批次。 **唯一 owner:** `POST /genealogy/app/notifications/{notificationId}/read` 持有单条已读,`POST /genealogy/app/notifications/read-all` 持有全部已读。两者无 request body,成功精确返回 `RVoid`;页面只把 `snapshotKey` 交给 notification controller,由 controller 私有解析服务端 ID。 **当前线上证据:** 两条 POST 已存在且返回 `RVoid`,但 path 和 `NotificationVo.notificationId` 都是 int64;最大值进入 JavaScript 会失真。`RVoid.code` 未 required,操作没有正式幂等、重试、超时未知、当前账号作用域、跨账号/不存在、read-all 截止点、并发新消息或刷新收敛语义。 **API-NOTIFICATION-STATE-001:无损身份和响应闭包。** `NotificationVo.notificationId` 与 path 参数必须同为 required 的 1—128 位 URL-safe opaque string,pattern 固定 `^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$`;禁止 int64 双读或解析后转字符串。`RVoid.code` 为 required integer,两个 POST 都要求 SaToken 和 required string clientid。 **API-NOTIFICATION-STATE-002:幂等与并发截止点。** 两个操作均对当前账号幂等,重复调用成功且没有重复副作用。read-all 以服务端接收请求时当前账号已存在的活动通知为截止集合,之后并发到达的通知保持未读;成功后客户端重取列表和 count。超时、断网、408/5xx 或畸形响应属于结果未知,允许依靠服务端幂等安全重试或先读取状态收敛,不能本地递减计数猜结果。 **API-NOTIFICATION-STATE-003:对象隔离和错误一致性。** 无效会话使用稳定 401;单条 ID 不存在或属于其他账号时统一返回 404 `NOTIFICATION_NOT_AVAILABLE`,避免暴露存在性。文档、部署、错误 envelope 与客户端 validator 必须一致,并以两个账号、删除消息、多端并发和迟到响应做反例。 **客户端迁移与关闭条件:** 写合同通过后,controller 才能私有保留 server ID,并原子删除读取首版“内部也完全丢弃 ID”的实现以及 N01/N02 的 clone mutation;公开页面模型、路由、日志和持久存储仍不得出现 ID。后端同版本 JSON/YAML 必须通过 `tests/notification-read-state-openapi-contract.ps1`,随后完成真实账号与 MuMu 的重复点击、提交中、失败/未知播报及 N01/N02/M01/G01 收敛矩阵。 ### 5.8 后端问题单 API-PROFILE-UPDATE-001—004 **优先级:** P1;阻塞 M02 真实保存和 profile 正式 remote 发布。Task28 的 GET 是前置依赖;头像、相册权限、OSS、性别、生日、密码和手机号均不属于本问题单。 **唯一 owner:** 继续使用 `PUT /genealogy/app/auth/profile`,不再增加 PATCH。operation 必须把自身定义为字段级原子 merge update:出现的可编辑属性更新,省略的可编辑属性保持不变;重复相同字段集只设置同一状态,不产生重复通知等额外业务副作用。请求专用 owner 命名为 `AppProfileMergeUpdateBody`,避免旧 `ProfileUpdateBody/AppProfileUpdateBody` 被误当资源替换。 **当前双版本证据:** 受保护双导出的 PUT 使用 `ProfileUpdateBody`,只有 nickName、avatarOssId、sex、birthday 和省市区,没有 realName/email;示例又含 schema 外 `regionCode/addressDetail`,成功返回 generic `RObject`。线上变为 `AppProfileUpdateBody`,含 nickName/realName/avatar/sex/birthday/email,但无 required、`minProperties`、关闭额外字段、merge/clear/version;200 为 `*/* → RAppProfileVo`,实体与 envelope 无 required,operation 无 security/clientid,只列 200/401。两者都不能证明安全写入;未发送真实 PUT。 **API-PROFILE-UPDATE-001:最小 dirty command。** `AppProfileMergeUpdateBody` 是 `additionalProperties:false`、`minProperties:1/maxProperties:3` 的对象,属性集合精确为 nickName/realName/email 且均非 required。nickName 出现时为无边界空白的 1—30 字符,空串/null 非法;realName/email 分别以 `oneOf` 区分精确 `""` clear 命令与非空规范值,非空 realName 1—30,email 1—100 且 format=email。省略保持;纯空白和边界空白拒绝,服务端清库后响应省略该属性。 **API-PROFILE-UPDATE-002:单一版本并发。** `AppProfileVo.profileVersion` required,固定为 1—128 位 URL-safe opaque string;PUT required `If-Match` 采用同形状,body 不重复 version。服务端以当前账号和租户做原子 CAS;成功返回新版本,旧版本固定 HTTP 409 与 `RProfileVersionChanged.businessCode=PROFILE_VERSION_CHANGED`,不得 last-write-wins。H5 正式 origin 的 CORS 必须允许 `If-Match`。 **API-PROFILE-UPDATE-003:typed 响应、认证、错误与隐私。** 200 精确 `application/json → RAppProfileVo`,envelope `code/data` required,data 是完整 canonical profile;400/401/409/422/429/500 均进入同版本文档。operation required SaToken 和 string clientid;GET/PUT 资料响应声明并实测 `Cache-Control: private, no-store`。422 只返回 nickName/realName/email 的结构化字段错误。客户端及服务端日志、路由、持久缓存、遥测和异常不得含真实姓名、邮箱或请求/响应 payload。 **API-PROFILE-UPDATE-004:结果未知与账号隔离。** timeout、network、408/5xx、取消和畸形 200 均视为 outcome unknown;客户端先 GET 对账本次脏字段,全匹配确认成功、仍为旧 baseline 才允许重试、第三值或无法归因版本进入 conflict。session generation 变化时清草稿并拒绝迟到响应;RequestTask 取消不表示服务端未写。 **页面迁移与关闭条件:** 首次 GET 后才建立 baseline,clean 不发请求,saving 冻结三输入和返回;成功应用响应并重置 baseline,失败/unknown/conflict 保留草稿。原子删除 `currentUser.name` 同时冒充昵称/实名、500ms 假保存、API 禁用旧测试断言、假头像按钮和“邮箱用于接收通知”无依据承诺。后端同版本双导出通过 `tests/profile-update-openapi-contract.ps1` 后,才依次实现 normalizer、API、M02 状态机;再用两个账号/多端并发、超时对账和正式 H5 CORS 验证,最终在 MuMu 检查键盘、TalkBack、错误聚焦、长文本、冲突与返回。 ### 5.9 后端问题单 API-LOGOUT-001—003 **优先级:** P1;阻塞 M10 服务端撤销和正式 remote 退出闭环。现有本机 `session.clear()` 仍保留为任何网络状态下的安全底线,但不能冒充服务端成功。 **唯一 owner 与当前证据:** `DELETE /genealogy/app/auth/logout` 无 body。受保护双导出有 required clientid、SaToken 和 200 `RVoid`,但只列 200、RVoid 无 required,未定义 scope/幂等/撤销传播。线上只有 200 RVoid 与 401 string,媒体为 `*/*`,operation 无 security/clientid;同样没有 scope、复用和其他设备反例。页面当前只执行一次本地清理并根跳转,相关测试没有远端请求、迟到 A/B 账号竞态或离线状态。 **API-LOGOUT-001:当前凭证族范围与撤销传播。** DELETE 只撤销 bearer 所属当前设备 credential family,包括同一登录会话的 refresh 能力;同账号其他设备 token 保持有效。200 必须表示撤销已传播至所有鉴权节点:旧 access 不能访问任一受保护接口,旧 refresh 不能换新 access。已经鉴权通过的并发业务请求不属于可回滚范围;全设备退出必须另接口。 **API-LOGOUT-002:唯一幂等成功和拒绝。** 能验证为该 client 历史签发的 active、revoked、expired credential 重复 DELETE 都返回相同 200 RVoid且无额外副作用。伪造、格式非法或 client 不匹配才返回 HTTP 401 `RLogoutRejected`,required `code/businessCode`,businessCode 只允许 `TOKEN_INVALID/TOKEN_CLIENT_MISMATCH`;这些拒绝不算远端撤销成功。不得长期并存 200 业务 401、HTTP 401 string 和 typed JSON 三种合同。 **API-LOGOUT-003:安全、媒体、缓存和反例。** operation required SaToken 与非空 string clientid,并验证 clientid 与 token client 绑定;200/401 为 application/json,`Cache-Control: private, no-store`,RVoid required integer code,另声明 400/429/500。以同账号两设备 token A/B 验证:A 删除后全受保护接口拒绝 A,重复 A 仍 200,B 保持有效;再验证 expired、伪造、client mismatch、跨鉴权节点传播、弱网/超时和正式 H5 Authorization/clientid CORS。服务端日志不得记录 bearer。 **客户端关闭后的唯一流程:** `logoutCoordinator` 同步捕获 A token/clientid/epoch,立即经 session owner bump epoch 并清全部账号态,再用显式 A 创建后台 RequestTask且立即 `goRoot(A01)`;M10 不持 token,请求不绑定页面 controller。coordinator 仅保存 attemptId/logoutEpoch/status,绝不在异步 finally 再 clear;A01 只在 session 为空且 epoch 未变时消费一次状态,B 登录后丢弃 A 迟到结果。所有分支都承诺“已从本机退出”,再区分 confirmed/unconfirmed/not-revoked;不持久 token、不跨重启重试、不阻塞重新登录。 **关闭条件:** 后端同版本 JSON/YAML 通过 `tests/logout-openapi-contract.ps1`;三人复核后才实现 session epoch、coordinator、严格 API 和 A01 提示,并原子替换 M10/导航/NM 旧静态断言。随后完成两设备部署矩阵和 MuMu 的确认、双击、系统返回、网络异常、状态播报、快速重新登录与根导航失败验收。 ### 5.10 后端问题单 API-PASSWORD-001—005 **优先级:** P0;同时阻塞 A01 密码登录、A04 注册、A05 找回后的新密码验证、M04 登录态改密与正式 remote 模式。当前 M04 本地预览不得冒充修改成功。 **当前三方证据:** 受保护双导出的 `PasswordLoginBody/PasswordRegisterBody/PasswordResetBody/PasswordChangeBody` 都把密码写成静态 32 个十六进制字符 MD5;改密虽有 SaToken/clientid,却只有 200 RVoid,RVoid 无 required。线上相应 `AppPassword*Body` 仍接受大小写 MD5,M04 只有 200 `*/* → RVoid` 与 401 string,operation 无 security/clientid;live server 还发布 HTTP URL。现有 M04 只有 500ms 本地定时器,旧测试正确禁止提前导入 API;未发送 PUT。 **API-PASSWORD-001:唯一 raw wire 与策略 owner。** 新增 `CurrentPasswordSecret` 和 `NewPasswordSecret` 两个共享 schema。登录 password 与改密 oldPassword 只能引用前者,1—64 Unicode code point、原样不 trim;注册、找回和改密 newPassword 只能引用后者,NFC 后 15—64 code point,允许空格/Unicode/粘贴/密码管理器且无组成规则。四条入口在同一版本删除 MD5 与任何 raw/hash oneOf fallback;服务端执行常见/泄露密码 blocklist、账号限速、新旧不同与带独立盐的 Argon2id,无法使用时才选合规 scrypt/PBKDF2。confirm 永不出端。 **API-PASSWORD-002:重新认证、ALL session 与原子 CAS。** M04 以当前密码重新认证,TAC 不能替代;严格 200 前在同一安全事务中写入新 verifier、递增账号 credentialEpoch,并跨节点撤销所有设备/所有 client 的既有 access、refresh 与 renewal session,包括调用者。两个同旧密码并发请求至多一个 200,另一个 typed 409 `CREDENTIAL_VERSION_CONFLICT`。不返回新 token,不保留旧 bearer。 **API-PASSWORD-003:typed 错误与确定未写边界。** PUT 声明 200/400/401/409/422/429/500;409/422 的 `RPasswordChangeRejected.businessCode` 精确为 `CREDENTIAL_VERSION_CONFLICT/CURRENT_PASSWORD_INCORRECT/NEW_PASSWORD_SAME_AS_CURRENT/PASSWORD_POLICY_VIOLATION`。400/422/429 明确保证未修改;401/409 进入重新登录;network/timeout/取消/畸形 2xx/5xx 均为结果未知,客户端不得自动重试或解析 msg。 **API-PASSWORD-004:鉴权、媒体、缓存与秘密卫生。** required SaToken、与 token client 绑定的非空 clientid、关闭额外字段的 JSON body;所有响应 application/json 且 `Cache-Control: private, no-store`,429 required `Retry-After`,RVoid integer code required。OpenAPI server 和实际重定向全程 HTTPS。反向代理、应用日志、APM、分析、崩溃报告与错误 body 不记录 old/new/confirm、MD5、Authorization 或完整请求。 **API-PASSWORD-005:账号能力和客户端崩溃边界。** 后端明确所有 App 账号是否都已配置密码;若不是,profile 返回稳定 `passwordConfigured` 并让无密码账号进入独立 step-up 设置流程,M04 不猜。客户端 session owner 在 dispatch 前只持久化 `{sessionEpoch,startedAt}` 的 `credentialChangeInFlight`;确定未写清 marker,200/401/409/unknown 清同 epoch 账号态并回 A01。冷启动同 epoch marker 在任何缓存渲染前 fail closed,新登录 bump epoch,迟到旧响应不得清新账号。禁止持久 token、密码、摘要、body、operation 状态或自动重试。 **关闭条件:** 后端同版本 JSON/YAML 通过 `tests/password-change-openapi-contract.ps1`,并先关闭密码登录 TAC 门禁;三人复核后按共享策略→四条 API wire→session epoch/marker→M04 状态机顺序原子实施,删除 `calcMD5` 生产消费者、8—32 旧规则和 NM preview 断言。最后以两设备全部 access/refresh 撤销、并发/fault injection、秘密日志扫描、正式 CORS/HTTPS 和 MuMu 的密码管理器、系统字号、TalkBack、44dp、错误聚焦、Android 返回与跨根提示验收。 ### 5.11 后端问题单 API-PHONE-001—005 **优先级:** P0;阻塞 M05、全活动短信码生产强度及正式 remote 模式,并依赖 M04 raw-password 和认证/TAC 门禁先关闭。当前 M05 只做本地 4 位码校验,不得冒充换绑。 **当前三方证据:** 本地 `PhoneChangeBody` 要求 `clientId/phone/smsCode`,线上 `AppPhoneChangeBody` 只要求 `phone/smsCode`;两边都是 4 位码,都没有 currentPassword、号码占用、并发、会话撤销、outbox 或结果未知语义。线上 PUT 无有效 security/clientid且返回完整 `RAppProfileVo`,错误只有 401 string;共享发码 operation 在线上明确忽略权限,当前客户端方法也固定不携 bearer。页面使用脱敏 fixture、70rpx `view role=button` 和 500ms 定时器,未调用 API/TAC;未发送 POST/PUT、短信,未操作 MuMu。 **API-PHONE-001:专用受保护发码 operation。** 新增 `POST /genealogy/app/auth/phone/sms/code`,required SaToken 与非空 clientid,闭合 `PhoneChangeSmsCodeBody` 只含 `phone/validToken`;服务端固定 scene=`APP_PHONE_CHANGE`,不接受 sceneCode/clientId/tenantId/grantType。公共 `/auth/sms/code` 删除该 scene。两个 operation 复用同一 OTP 服务 owner;匿名专用调用必须 401,公开登录/注册/找回发码仍可匿名。validToken 必须绑定当前账号/session、tenant、client、scene 与规范化新号并单次消费。 **API-PHONE-002:唯一六位 OTP wire 与生命周期。** 新增 `SmsCodeSecret`:CSPRNG 生成恰好 6 位 ASCII 数字、保留前导零、writeOnly、无示例;5 分钟 TTL、60 秒重发、最多 5 次失败、单次消费,重发废止旧 generation且不重置累计失败次数。同一复合键只有一条 active generation。A01/A04/A05/M05、`AccountDeactivateBody` 及同源生成器、短信模板、双导出、validator、页面和测试同版删除全部 4 位规则,不保留 4/6 fallback。 **API-PHONE-003:existing-factor 与闭合最终 PUT。** `PUT /genealogy/app/auth/phone` required SaToken/clientid,`PhoneChangeBody` 只含 required `currentPassword/phone/smsCode` 且关闭额外字段;密码引用 `CurrentPasswordSecret`,新号引用 11 位 `NewBoundPhone`,短信引用 `SmsCodeSecret`。当前密码是既有因子再认证,TAC 不能代替;无密码账号返回 `STEP_UP_UNAVAILABLE` 进入独立恢复,不能降级为 bearer+新号 OTP。不要求旧号 OTP,成功后改用旧号安全通知。 **API-PHONE-004:原子换绑、唯一约束与会话。** 在一个事务中验证 currentPassword/active OTP、执行 `(tenantId,canonicalPhone)` 唯一约束、消费 OTP、CAS 更新号码、递增 credentialEpoch、撤销包括当前在内的全部 access/refresh/renewal session,并持久化旧号通知 outbox;严格 200 只返回 `RVoid`。并发至多一笔成功;通知投递失败不回滚换绑,但 outbox 必须重试并告警。不得在证明新号控制权前泄露号码是否已绑定。 **API-PHONE-005:typed 错误、传输与客户端恢复。** POST/PUT 都声明 200/400/401/409/422/429/500 JSON、`private, no-store`,429 有 `Retry-After`;409/422/429 使用 required `RPhoneChangeRejected.code/businessCode`,稳定覆盖 current password、同号/占用、验证码错误/过期/尝试耗尽、credential 冲突、step-up 不可用、验证重做与限流,客户端不解析 msg。最终 PUT dispatch 前复用无秘密 `{sessionEpoch,startedAt}` marker;200/401/409/unknown 清同 epoch 账号态回 A01,不自动重试。HTTPS、Authorization/clientid CORS 和密码/手机号/OTP/TAC/token 全链路日志脱敏必须实测。 **关闭条件:** 同版本 JSON/YAML 通过 `tests/phone-change-openapi-contract.ps1`,且认证、密码和 profile 读取前置门禁全部通过;三人复核后按全活动六位码→专用发码 API→共享 credential marker→M05 状态机原子实施,替换旧四位/preview 断言。最后完成匿名/错场景/TAC 重放、前导零、重发/过期/限流、号码唯一与枚举、两设备并发、全部 session 撤销、fault injection、旧号 outbox 和 MuMu 的输入法、TalkBack、44dp、系统返回与结果未知矩阵。 ### 5.12 后端问题单 API-G03-001—005 **优先级:** P0;阻塞 G03 真实创建、创建后 G01/G05/context 闭环及 APP 家谱访问规则唯一化。当前同页两步是明确本地预览,不得把 `local-created-*` 或 fixture mutation 当作后端成功。 **当前三方证据与方案结论:** 本地 `GenealogyCreateBody` 和线上 `AppGenealogyCreateBody` 都只创建家谱,通用人物 POST 另写始祖;创建响应未形成 required 词法 ID/OWNER/READY 回执。页面缺可信 regionCode,默认男性、硬限 1800 年、把“一世”混入 generationName;成功不安装真实 context。三人先设计 `ROOT_REQUIRED` 两写及恢复,再确认没有跨库或保存空谱需求,最终否决这类客户端 saga:它只会新增半成品配额、可见性、删除/过期、版本、G01 恢复卡和第二次未知结果。唯一最小生产方案是最终按钮一次原子 bootstrap,第一步零网络写。 **API-G03-001:闭合 bootstrap 与领域事务。** `POST /genealogy/app/genealogies` 唯一 body 改为 additionalProperties=false 的 `AppGenealogyBootstrapBody`,字段精确为 `genealogyName/surname/ancestralHall/regionCode/accessPreset/rootPerson`,除堂号外全部 required;rootPerson 只含 `name/sex/birthDate/biography` 且前两项 required。sex=`MALE/FEMALE/UNKNOWN`,生日 format=date,服务端固定 generation=1、唯一首根且不接收账号/编号/父母/字辈/状态。严格 200 前一个事务完成 quota、谱、OWNER、根、READY 和幂等回执,失败全回滚;通用人物 POST 仅用于 READY 后普通人物。同名不是冲突,重复提醒只做建议。数据库 bootstrap-root marker 是身份权威:根 PUT 可编辑白名单精确只有 `name/sex/birthDate/biography`,status/personStatus、账号绑定、世代、父母、根标记及任何白名单外字段一律 422;collection POST、人物 DELETE 和 parents mutation 也不能创建第二根、删除根或给根重挂父母。 **API-G03-002:幂等键、控制事务、结果和稳定错误。** required `Idempotency-Key` 引用 `GenealogyBootstrapOperationKey`,精确格式为 `gcb.{13位 issuedAt 毫秒}.{22—43位 base64url CSPRNG}`,随机量至少 128 位;固定 `acceptUntil=issuedAt+10 分钟`,以 server time 判定,未来超过 5 分钟返回 400 `OPERATION_KEY_INVALID`,并以 600/300 秒 extension 锁定。窗口内首次 POST 用短控制事务按 account/tenant/client/path/key 唯一 CAS 认领 PENDING、canonical digest、fencing lease 与 `resolveBy<=claimedAt+2 分钟`,以 120 秒 extension 锁定;相同作用域/key/body 的已存在操作在截止后仍返回同一结果,不同 digest 返回 409 `IDEMPOTENCY_KEY_REUSED`,过期且不存在的 key 返回 409 `OPERATION_KEY_EXPIRED`。业务事务才原子处理 quota、谱、OWNER、唯一根、READY 和 SUCCEEDED;失败回滚后 CAS FAILED_NO_COMMIT,watchdog 同样用 fencing CAS,旧 worker 不能迟交。`GenealogyBootstrapResult` required 词法字符串 genealogyId/rootPersonId、setupState=READY、roleType=OWNER、canView=true;成功防重记录至少覆盖实体生命周期。POST 声明状态专属、`code` 与 HTTP 状态单值一致的 400/401/403/409/422/429/500 typed JSON、private/no-store,429 有 Retry-After。 **API-G03-003:无 PII operation-status 与迟到竞态。** 新增 required SaToken/clientid 的 `GET /genealogy/app/genealogy-bootstrap-operations/{operationKey}`,有效参数只有 operationKey/clientid且没有 request body,响应集精确为 200/400/401/404/429/500,禁止泄漏性 403/default。响应以带显式 mapping 的 discriminator `oneOf` 关闭为 `PENDING{resolveBy,retryAfterSeconds}`、`SUCCEEDED{result}`、`FAILED_NO_COMMIT`,三个 status 均为单值 string;`x-state-transitions` 精确登记 `ABSENT→PENDING→SUCCEEDED/FAILED_NO_COMMIT`,两个终态无出边且 `x-terminal-immutable=true`,FAILED 同时固定 `x-domain-effects=NONE/x-quota-consumed=false`。PENDING 的 retryAfterSeconds 为 1—30,200 不强制 Retry-After。GET 必须纯读且始终无副作用:acceptUntil 前无记录返回 typed 404 `BOOTSTRAP_OPERATION_NOT_AVAILABLE`、服务端 acceptUntil 和 Retry-After,客户端保持 unknown;截止后无记录按 key 可计算地返回 FAILED_NO_COMMIT,不写 tombstone,迟到 POST 永久拒绝。PENDING 最迟 claimedAt 后 2 分钟终结;SUCCEEDED 记录至少保留实体生命周期,FAILED 至少 30 天;跨 account/tenant/client 统一不泄漏 404。响应不含原请求或人物 PII,operation 不进 `/mine`、不占业务 quota;GET 零写与 FAILED 零领域提交仍必须另以 DB 观测测试证明。 **API-G03-004:APP 访问预设单一 owner。** 新 `GenealogyAccessPreset` 只允许 MEMBER_ONLY/PUBLIC_APPLY,并在同一版本成为 `AppGenealogyBootstrapBody`、实际 `/mine`/overview 读取所用 `AppGenealogyVo` 和闭合 `AppGenealogySettingsUpdateBody` 的唯一访问字段。删除 `GenealogyCreateBody/AppGenealogyCreateBody/GenealogyUpdateBody/AppGenealogyUpdateBody` 旧入口以及 visibility/joinMode;不保留 oneOf fallback、数字 pair 或邀请码 mode 的暗中映射。validator、runtime、双导出、fixture 迁移、G03/G05/G11 测试和文档同批更新。这里只关闭共享字段迁移;G11 写入仍须另行完成 If-Match、版本/CAS、权限刷新和结果未知门禁。 **API-G03-005:可信地区、始祖不变量、认证与部署反例。** `GenealogyRegionCode` 是 1—32 位 URL-safe 词法标识;唯一地区 owner 改为 required SaToken/clientid 的 `GET /genealogy/app/region/search`,同版删除旧公共 `/genealogy/region/search`。keyword required 且 minLength=1/maxLength≤50;`RListRegionSelectVo.code/data` 和 `RegionSelectVo.regionCode/label/selectable` required,leaf 不等于 selectable,不强制层级;页面只展示 label、提交 code,POST 在业务事务中复验仍可选。通用人物写入以 typed 409 `GENEALOGY_NOT_READY` 和 422 `BOOTSTRAP_ROOT_IMMUTABLE` 覆盖 collection/PUT/DELETE/parents;PUT operation 以 `x-bootstrap-root-editable-fields=[name,sex,birthDate,biography]` 和 `x-bootstrap-root-noneditable-policy=REJECT_422_BOOTSTRAP_ROOT_IMMUTABLE` 精确锁定仅四项可编辑,其余字段一律 422。create/status/settings/region及相关人物私有响应必须是 JSON+private/no-store;同一后端模型重导后先由 `openapi-yaml-json-parity-runtime-smoke.js` 以无损任意精度数字和严格 YAML mapping 语法深比较完整 JSON/YAML,再递归检查组合 schema 字段。以匿名、错 client、跨账号 status、unsafe 数字 ID 差一、地区失效、quota race、同 key 并发、control/business/terminal 各写点 fault injection、GET 零写、根 PUT 白名单及其他绕过、超时/5xx/畸形响应和正式 HTTPS/CORS 验证文档与部署一致。 **客户端关闭后的唯一流程:** 第一步只校验并进入页内始祖步骤;最终校验后冻结 canonical snapshot,先持久 `{sessionEpoch,operationKey,startedAt}` 再 POST。本地校验或可证明零发出的 request-build 失败不留 marker;服务端在 claim 前返回的 400/401/403 清 marker,401 同时清会话;`IDEMPOTENCY_KEY_REUSED` 进入 fatal/quarantined,不查装 status、不自动换 key,只有用户看到警告并显式放弃才清;确定未提交的 limit/expired/422 可清。429 保持同 key/body并先查 status;500/network/timeout/408/发出后取消/意外 2xx/3xx/畸形 200 都保持 marker按 unknown 查询。status 截止前 404 保持,400 清损坏 marker,401 走会话失效,429/500/network/cancel/unexpected/malformed 保持退避,FAILED_NO_COMMIT 才允许新 key。冷启动只查 status,不保存姓名/生日/生平、完整 body或可逆日志。SUCCEEDED 唯一次序为 committed receipt → 失效或定点更新 `/mine` → 安装 context → G05;context/导航失败不重发创建。落地时删除 local preview/mock create 与旧禁止 API 断言,不能长期并存两个创建 owner。 **页面与关闭条件:** 地区搜索、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 返回矩阵全部通过。 当前其余已知但尚未核实的重点依赖包括:微信登录、公共行为验证、完整短信状态机、邀请码验证与直接加入、结构化亲属关系、上级家谱与支系权限、管理员授权与功能开关、上传与系统权限、消息业务目标、系统分享、订单支付与退款。它们只表示审查重点,不预判后端一定缺失。