149 KiB
接口与页面映射总表
更新日期:2026-07-22
阶段状态:阶段 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 差异核对;其余操作仍待逐项审查
一、权威边界
本文件是活动页面、业务目标、进入方式、返回或完成目标、适用状态与接口归属关系的唯一总表。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 是唯一活动登录页,承载密码登录、短信登录、注册、找回密码、协议入口和尚未接入的微信登录入口。当前默认密码登录;开发/联调 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的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 密码联调入口暂借现有 TAC 场景做客户端前置后调用线上密码登录 wire,但validToken未进入或被服务端消费;它不是安全闭环,API-AUTH-TAC-001仍阻止 remote 配置成为生产发布配置。 - 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并对未知组合失败关闭。任务 38 已固定唯一 merge PUT、fresh overview baseline、settingsVersion+If-Match+typed 409、24/80 code point、活动 PENDING 迁移阻断和 unknown 对账;后端门禁通过前不删除诚实本地预览或接宽松接口,见 2.16/5.15。任务 39 已把 G12 收紧为唯一 GET/PUT、ACTIVE-only 完整集合、poemSetVersion+If-Match、完整候选校验、软停用与 unknown 三方对账;后端绿前仍只做本地预览,见 2.17/5.16。
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 三个根页面与主要流程
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仍是 JSONinteger/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,且缺版本、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}+disableMissing,generationNo 唯一排序且声明严格升序;新行省略 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 差异与运行边界
- 新运行基址为
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 已开放开发/联调 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 创建:相同幂等键返回原结果,并发或不同键重放不能创建第二个任务;跨手机号、场景、租户或客户端全部失败。- 无障碍不得成为降风控布尔开关:禁止
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和 multipartfile;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 canonicalphone;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_CHANGETAC/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;GenealogySettingsVersion+If-Match+409与 T01/M02 并发模型一致,PUBLIC_APPLY 关闭时与普通申请准入串行化。当前G11-SETTINGS-OPENAPI-CONTRACT BLOCKED,见 5.15;后端绿只允许开始客户端 TDD,不允许直接接页。 - 字辈集合:Task39 固定唯一
appGetGenerationPoemSet/appUpdateGenerationPoemSet、GenerationPoemSetVersion+If-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可选且无 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。
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。
搜索投影只保留识别家谱所需字段与单一 viewerState;mine 以 PENDING/APPROVED/REJECTED/WITHDRAWN discriminator 表达,只有拒绝分支必带申请人可见 rejectionReason;pending 精确为 applyId/applicantName/relationDesc/applyReason?/submittedAt,递归禁止 phone、user、inviter、auditor。申请体只含 applicantName/relationDesc/applyReason?,审核只含 APPROVE 或 REJECT+rejectionReason;统一 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 receipt;FAILED_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 脏属性,服务端事务内重验管理权限并原子 merge;canonical no-op 返回当前 200、版本不升且无领域副作用。并发唯一使用 AppGenealogyVo.settingsVersion 和 required If-Match,body 不重复版本;版本以 x-version-scope-fields 锁定只随三个 canonical 设置实际变化。旧版本返回 typed 409 GENEALOGY_SETTINGS_VERSION_CHANGED+current AppGenealogyVo,不用 ETag/412;409 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 ref,Cache-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 重验 canView,PUT 事务内重验 tenant、canEditContent、READY 和版本。
AppGenerationPoemSetUpdateBody 精确 required {items,disableMissing}。声明行最多 500 且严格升序;已有行 poemId 必须来自 baseline ACTIVE,新行省略 ID,稳定 ID 可随显式 generationNo 移动。false 保留未声明 baseline ACTIVE,true 软停用遗漏项;空 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 为 GenerationPoemSetVersion+If-Match,body 不复制版本,不用 ETag/412;no-op 保持版本,ACTIVE 语义变化生成永不复用的新版本。unknown 以 baseline/version、declaredItems 和 disableMissing 构造 effective target,按 FRESH_GET_THREE_WAY_NO_AUTO_PUT fresh GET:existing 比较 ID+generation+NFC 文本,NEW 比较 generation+文本并要求返回 ID 新鲜;同基数且无额外 ACTIVE 行才相等。先执行 CURRENT_EQUALS_TARGET_FIRST_NO_ATTRIBUTION,只陈述当前事实;再判 old,否则 divergent,全部不自动 PUT。
响应只允许 typed JSON、private/no-store,429 带 Retry-After;SaToken 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-store,429 带 Retry-After,required 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、发送中、倒计时、请求取消、登录失败、凭证过期 | 认证与账户 | 密码登录线上 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.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 空态或添加家谱弹层 | 普通搜索按 viewerState 进入 G05/G08/G09;邀请成功刷新 workspace/context 后进入 G01/G05 | 搜索六关系;邀请码输入、格式错、解析、目标、确认、PENDING/unknown、已加入、普通申请冲突、失效、限流 | 普通搜索与独立邀请 | Task36 搜索见 2.14/5.13;Task37 六 operation 邀请红灯见 2.15/5.14,门禁前不接远端 |
| G08 | 关系确认与入谱 | pages/genealogy/g08-join-application |
只提交普通加入申请;真实接线时删除全部 invite source | G06、G05 或 G09 的普通申请入口 | 普通申请成功刷新 G09 | 不可申请、字段校验、PENDING、unknown、成功、失败、放弃 | 普通加入申请 | Task36 后端红灯见 2.14/5.13;Task37 明确邀请不得进入 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 审查 |
| 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 | 加载、列表、空、失败、无有效家谱、跨谱失败关闭 | 家族内容聚合 | 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 | 加载、正常、内容失效、失败、评论校验、本地预览、无权限 | 动态与评论 | 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 回 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 |
从 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 |
四、封存与已移除页面
| 页面 | 当前决定 | 合同边界 |
|---|---|---|
| A02 账号登录 | 已完整并入 A01,不保留兼容路由 | 任何注册、重设或退出后的登录目标统一指向 A01 |
| A03 | 已移除 | 不恢复无明确业务职责的历史入口 |
| A06 登录状态 | 源码保留、活动路由封存 | 未来只有账号冻结、停用或风险限制等无法继续登录的阻断状态,且产品重新确认独立页面后才能恢复 |
| G02 | 并入 G01 空状态 | 搜索、邀请码加入和创建三个入口由 G01 空状态承载 |
| G04 | 并入 G03 始祖步骤 | 创建家谱与录入始祖属于同一可恢复流程 |
| G07 | 并入 G06 | 搜索、筛选、结果与无结果均由 G06 双模式承载 |
| T02 | 并入 T01 | 世系阅读提示、空态和失败态不再拆分独立路由 |
五、后续接口审查填充规则
每个对应业务阶段都必须先解析并比对两份 OpenAPI 导出,再按三人独立首审、交叉补漏、反向质询和共同收敛的顺序更新本文件。每个页面和用户操作都要补齐以下事实:
- 接口分类:App 可直接使用、PC 专用、App/PC 可能共用待确认、App 合同不完整、App 缺失、页面无合理用途或双方需重定义。
- 精确合同:路径、方法、鉴权、请求参数、字段类型、必填性、可空性、枚举、响应模型和错误码。
- 数据行为:分页、排序、筛选、上传、幂等、防重复提交、并发冲突、权限和数据副作用。
- 页面结果:成功、失败、取消、返回、完成、来源页刷新、重复进入和目标数据过期时的表现。
- 证据与状态: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 原子更新:
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;版本变化返回 HTTP409和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 三类计数之和等于总量。可见根与 bucketfocusPersonId只能使用 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/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/401,401 schema 为 */* string,也没有有效 security 声明。无令牌行为已经拒绝访问,因此不能仅凭注解缺失断言已发生公开泄漏;对象级授权仍没有有效账号反例证据。
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 用 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:typed HTTP、缓存与对象级授权。 两条读取都 required SaToken 和非空 clientid;/mine 精确 200/401/429/500,overview 精确 200/400/401/404/429/500,不再允许 HTTP 200+业务错误。400 固定 GENEALOGY_ID_INVALID,401 为 AUTH_REQUIRED,对象无权/撤权/不存在以 NON_DISCLOSING_GENEALOGY_NOT_AVAILABLE 统一 404,429/500 分别为 RATE_LIMITED/GENEALOGY_WORKSPACE_UNAVAILABLE。全部只允许 JSON;每个响应必须含固定单值 private/no-store,429 另有 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。
关闭条件: 后端从同一版本重新导出 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 返回矩阵全部通过。
当前其余已知但尚未核实的重点依赖包括:微信登录、公共行为验证、完整短信状态机、邀请码验证与直接加入、结构化亲属关系、上级家谱与支系权限、管理员授权与功能开关、上传与系统权限、消息业务目标、系统分享、订单支付与退款。它们只表示审查重点,不预判后端一定缺失。
5.13 后端问题单 API-JOIN-001—006
优先级: P0;阻塞 G06/G08/G09/G10 普通申请、撤回和审核的真实闭环。邀请码直入不属于本问题单。
API-JOIN-001:七个稳定 owner、词法身份与 cursor。 发布设计 23.10 的七个 operationId;三个列表分别返回专用 page/item,不返回 total,limit 1—50,cursor 绑定 tenant/account/client/filter;搜索按 updatedAt+genealogyId,mine/pending 按 submittedAt+applyId 稳定排序。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 或 REJECT+required 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-store,429 和 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 六 operation,required 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,相同撤销重放 200,CONSUMED/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:安全、无障碍与发布。 所有响应 JSON+private/no-store,429 带 Retry-After;URL/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 且不新增 PATCH;overview 是 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 pending,409 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-005:typed 响应、unknown 与缓存。 精确 200/400/401/403/404/409/422/429/500,只允许 JSON;每个 response 必须含单值 enum private/no-store,429 另有 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/items,ACTIVE-only 且按 generationNo 升序;GET 重验 canView,PUT 事务内重验 tenant/canEditContent/READY。
API-POEM-002:完整候选、软停用与原子写。 body 唯一为 closed required AppGenerationPoemSetUpdateBody {items,disableMissing}。items 0—500 严格 generationNo 升序;已有 ID 只允许 baseline ACTIVE 且可显式 move,新行省略 ID,disabled/unknown ID 失败,重复文本允许。false 保留遗漏 baseline,true 软停用;empty false no-op,empty true 停用全部。先校验声明严格升序,再构造声明目标与完整 merged candidate,随后按固定步骤验证唯一/slot/连续/最终容量、分配并校验新 ID,最后原子写;GENERATION_SLOT_CONFLICT 与所有校验失败零写,swap 必须显式声明全部受影响行,历史绝不物理删除。
API-POEM-003:版本 CAS 与 Unicode。 GenerationPoemSetVersion 是 1—128 位 opaque、不可解析、永不复用的 ACTIVE 语义版本,PUT required If-Match,body 无版本,禁止 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-004:typed HTTP、security 与部署 CORS。 GET 精确 200/400/401/404/429/500,PUT 另有 403/409/422;全部 typed application/json、private/no-store,429 带 Retry-After,禁止 default/3xx/*/*/通用 envelope/200 包业务错。SaToken 精确为 Authorization header apiKey,operation 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 GET,collection 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-004:keyset cursor 与读取窗口。 cursor? + limit?,默认 20、1—50,无 total/pageNum;nextCursor 缺席即结束。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-005:typed HTTP、安全与部署。 三 GET 精确 200/400/401/404/429/500,只允许 closed typed application/json、private/no-store;429 Retry-After。SaToken 必须是数组且空 scopes,clientid 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 页面未修改。