完成50%
This commit is contained in:
+607
-350
File diff suppressed because it is too large
Load Diff
+221
-25
@@ -1,7 +1,7 @@
|
||||
# 家谱项目全量治理设计
|
||||
|
||||
> 日期:2026-07-22
|
||||
> 状态:阶段 0 已完成;导航栈语义与 T01 大规模世系树设计已经三人终审,尚未实施业务代码
|
||||
> 状态:阶段 0 已完成;导航任务 1—10 的静态实施和零债务门禁已经完成;A01/A04/A05 TAC 客户端与 M07 反馈客户端已经完成;MuMu 原生矩阵待执行;T01、认证、家谱工作区、M06 帮助、个人资料读写、通知读写、M10 服务端退出、M04 密码凭证与 M05 手机号换绑后端合同均处于硬门禁红灯
|
||||
> 适用范围:当前 UniApp 家谱项目、根目录 OpenAPI 文档、全部活动页面、共享组件、测试、正式资产与后续真实接口接入
|
||||
|
||||
## 一、背景
|
||||
@@ -509,7 +509,7 @@ MuMu 验证中
|
||||
|
||||
### 20.1 当前证据
|
||||
|
||||
只读扫描得到以下统一口径:
|
||||
任务 3 实施前的只读扫描得到以下统一口径:
|
||||
|
||||
- 52 个活动页面中共有 `navigateTo 59`、`navigateBack 11`、`redirectTo 14`、`reLaunch 7`,合计 91 次直接调用。
|
||||
- 活动共享组件另有 `navigateBack 2`、`reLaunch 2`,活动页面与组件共 95 次。
|
||||
@@ -517,6 +517,8 @@ MuMu 验证中
|
||||
- MuMu 已复现 F01→F03→“返回家族圈”后留下两个 F01,A01→A04→“登录”后留下两个 A01,T03 可连续叠出三个同路由页面。
|
||||
- 栈深为 1 时直接进入 F03,当前 `PageHeader` 会回到固定 G01,而不是业务父页 F01。
|
||||
|
||||
任务 3 已把共享页头和自定义底栏的直接调用清零,并删除无活动消费者的通用页面旧入口;任务 4—8 依次迁移认证、G、T、F、R,任务 9 完成 N/M、安全通知目标、账号表单返回守卫与退出会话清理,任务 10 删除最后一个零生产消费者的 `TreeMemberForm.vue` 及其旧专属合同。当前 SFC 分段词法扫描的迁移债务已经清零:pages/components 中 `navigateTo`、`navigateBack`、`redirectTo`、`reLaunch`、`getCurrentPages` 与业务页面路径字面量均为零,声明式 navigator、动态 Uni 属性、Uni 对象逃逸与 `switchTab` 也为零;验证结果为 `MIGRATION-DEBT=0`。路由与页面栈合同分别只由 `utils/navigation-routes.js` 和 `utils/navigation.js` 持有,后续业务批次不得恢复页面私有路径或栈判断。
|
||||
|
||||
因此问题不是某几个按钮写错,而是项目没有统一表达“打开页面、替换步骤、返回来源、完成流程、切换根页和直接进入回退”的语义合同。
|
||||
|
||||
### 20.2 八类问题的五方案终选
|
||||
@@ -537,7 +539,7 @@ MuMu 验证中
|
||||
### 20.3 唯一所有者
|
||||
|
||||
- `utils/navigation-routes.js` 是 52 个活动路由的唯一语义注册表,拥有路由键、路径、页面类型、规范父页、父页参数映射、根页、必填参数、可选参数、允许来源和目标页允许消费的结果操作枚举。
|
||||
- `utils/navigation.js` 是项目唯一允许调用 `uni.navigateTo`、`uni.redirectTo`、`uni.reLaunch`、`uni.switchTab` 和 `uni.navigateBack` 的业务模块。
|
||||
- `utils/navigation.js` 是项目唯一允许调用 `uni.navigateTo`、`uni.redirectTo`、`uni.reLaunch` 和 `uni.navigateBack` 的业务模块;项目没有原生 tabBar,`uni.switchTab` 在该模块内外都禁止。
|
||||
- 页面和组件只调用语义方法,不拼接页面路径,不保存 fallback URL,不接收后端原始跳转 URL。
|
||||
- 注册表中的路径集合必须与 `pages.json` 的 52 个活动路由精确相等;缺失、重复和陈旧条目均使合同失败。
|
||||
|
||||
@@ -549,27 +551,27 @@ MuMu 验证中
|
||||
openPage(routeKey, params, sourceKey)
|
||||
打开普通子页;sourceKey 必须等于当前真实页面。当前已经是同一路由且关键参数相同则不重复入栈。
|
||||
|
||||
replaceStep(routeKey, params, sourceKey)
|
||||
只替换同一流程中的临时步骤;sourceKey 同样必须来自当前真实页面,禁止用于“返回列表”。
|
||||
|
||||
goBack()
|
||||
栈内有上一页时 navigateBack;没有时按当前路由的规范父页逐级回退。
|
||||
|
||||
returnTo(routeKey, targetParams = {}, result = null)
|
||||
按路由键寻找最近实例并精确返回;targetParams 只用于目标不在栈内时构造合法回退 URL。
|
||||
returnTo(routeKey, targetParams = {})
|
||||
按路由键寻找最近实例并精确返回;显式 targetParams 必须与最近实例一致,目标不在栈内时才用于构造合法回退 URL;普通返回不产生流程结果。
|
||||
|
||||
finishPage(routeKey, targetParams, result)
|
||||
校验目标参数和类型化结果后调用 returnTo;结果只能包含 operation、entityId 和 refresh。
|
||||
是完成并回传结果的唯一公开入口;调用方必须显式提供目标全部必填参数,当前来源页与目标页共同声明且实际存在的上下文字段必须相等;校验参数和类型化结果后精确返回,结果只能包含 operation、entityId 和 refresh。
|
||||
|
||||
goRoot(routeKey, params)
|
||||
只接受 A01、G01、F01、M01 四个根语义;使用 reLaunch 清理旧流程。
|
||||
|
||||
handleBackPress(event, requestBack)
|
||||
同步适配 UniApp 的 onBackPress:网关自己的 navigateBack 回调来源返回 false 放行,其余来源同步返回 true,并异步执行页面唯一 requestBack,避免递归拦截。
|
||||
```
|
||||
|
||||
六个公开语义方法中,除纯判断外的导航动作都返回 `Promise`,并共享一个在途转场锁:同一目标的重复调用复用同一个 Promise,其他并发转场明确返回忙碌结果。导航失败或被锁拒绝时必须回滚刚写入的一次性结果。
|
||||
五个公开导航语义方法都返回 `Promise`,并共享一个在途转场锁:相同参数和相同结果语义的重复调用复用同一个 Promise,其他并发转场明确返回忙碌结果。一次性结果写入必须与“取得新锁”原子发生;复用或忙碌调用不得改写结果,导航失败必须回滚。每次转场用独立 flight 身份释放锁,`success/fail` 必须在 Promise settle 前释放自己的 flight;迟到的 `complete` 只能清理原 flight,不能清掉已经开始的新转场。所有调用方导航参数、流程结果和返回守卫上下文只接受普通对象的 own enumerable data properties;Symbol、访问器、不可枚举字段和原型继承字段一律拒绝,校验后只使用同一次读取形成的冻结快照,禁止重复 getter 读取或校验后别名篡改。`redirectTo` 只允许由网关内部的规范父链回退和目标缺栈返回使用;当前没有可公开授权的“替换步骤”边,因此不暴露无消费者的替换方法。
|
||||
|
||||
一次性结果只存在于当前 JavaScript 进程,目标页消费一次后立即删除。字段键精确为 `operation、entityId(可选)、refresh`;`operation` 必须属于目标路由注册的 `resultOperations`,`entityId` 若存在必须为非空字符串,`refresh` 必须是布尔值。它不是领域数据持久化,不允许加入 `treeVersion、genealogyId、focusId、payload`,也不允许保存完整对象、表单内容、列表快照或接口响应。
|
||||
一次性结果只存在于当前 JavaScript 进程。网关用模块内 `WeakMap` 为页面实例分配原始值身份令牌,结果 envelope 只保存令牌,不强引用或保存页面/Vue 实例。栈内返回时结果绑定反向搜索选中的最近目标令牌和该实例的规范业务参数;缺栈重建时绑定发起页令牌与完整目标参数。只有当前真实栈顶正是目标实例(或缺栈重建出的非发起页)且业务参数完全一致时才能消费,错页、同路由的更远实例和其他家谱上下文只能得到 `null`,也不得删除正确结果。结果必须在原生目标页 `onShow` 发生前随取得转场锁原子写入,消费一次后立即删除;新完成流程取得锁时淘汰已经错过目标生命周期的未消费旧结果,失败时不复活陈旧结果。字段键精确为 `operation、entityId(可选)、refresh`;`operation` 必须属于目标路由注册的 `resultOperations`,`entityId` 若存在必须为非空字符串,`refresh` 必须是布尔值。它不是领域数据持久化,不允许加入 `treeVersion、genealogyId、focusId、payload`,也不允许保存完整对象、表单内容、列表快照或接口响应。
|
||||
|
||||
`sourceKey` 只证明当前 JavaScript 进程内的一次真实导航:`openPage/replaceStep` 必须核对它与 `getCurrentPages()` 的当前真实路由一致。栈深为 1 或外部直接进入时,查询串里的 `sourceKey` 一律视为不可信并忽略,只按注册表的 `parent/parentParamMap` 建立回退目标;外部链接不能伪造来源合同。
|
||||
`sourceKey` 只证明当前 JavaScript 进程内的一次真实导航:`openPage` 必须核对它与 `getCurrentPages()` 的当前真实路由一致。栈深为 1 或外部直接进入时,查询串里的 `sourceKey` 一律视为不可信并忽略,只按注册表的 `parent/parentParamMap` 建立回退目标;外部链接不能伪造来源合同。
|
||||
|
||||
### 20.5 根页与页头
|
||||
|
||||
@@ -599,20 +601,22 @@ goRoot(routeKey, params)
|
||||
- A04 取消或“已有账号”返回 A01;注册并建立会话后 `goRoot(G01)`。
|
||||
- A05 取消返回 A01;只有未来真实重设接口成功才写入 `password-reset` 结果并回 A01。本地 `state=success`、计时器或视觉占位成功态只能普通返回,不得生成业务成功结果。
|
||||
- A01 登录成功 `goRoot(G01)`;会话失效和退出成功也只允许 `goRoot(A01)`。
|
||||
- F02、F03 完成或返回精确回 F01;F06 新建回 F04,编辑回 F05;F09 上传完成回 F08。
|
||||
- 当前 F02、F06、F07、F09 只形成明确标注“尚未提交服务器”的独立本地预览;F03 评论草稿不插入评论列表、不清空、不增加计数,F05 收藏明确禁用,F10 明确未开放。F01—F10 均不产生写成功结果;F02/F03 返回精确 F01,F06 新建回 F04、编辑回精确 F05,F09 无结果回同一 `genealogyId + albumId` 的 F08。真实写接口返回服务器 ID 后才能同轮启用完成结果。
|
||||
- `data/mock.js` 是 F 系列 feed/article/album 只读夹具的唯一 owner,只公开按 `genealogyId` 列表和按复合身份详情的深拷贝查询;跨谱、未知实体或缺失身份失败关闭。`utils/api.js::createFeed` 在 mock 模式以 `WRITE_UNAVAILABLE` 拒绝,不得修改列表冒充发布成功。
|
||||
- R02 回 R01;R04 回 R03;R06、R07 回 R05。
|
||||
- T04 保存后回 T01 并定位新成员;T05、T08 回当前 T03;T06 保存后回 T01 并刷新原焦点;T07 选择成员后回 T01 定位或进入 T03。
|
||||
- 当前 T04/T05/T06 只生成“尚未提交服务器”的本地预览,不产生写成功结果;用户确认放弃预览后,T04/T06 无结果回 T01,T05 使用 `goBack()` 精确回原 T03 实例,T08 普通返回 T03,T07 可进入 T03。新增亲属、编辑成员和编辑关系的完成定位只能在真实版本化写接口同轮启用。
|
||||
- 当前阶段 `data/mock.js::treeMembers` 是唯一可变成员夹具 owner,`utils/api.js` 是唯一写入口;T01/T03—T08 只能通过 `listTreeMemberFixtures(genealogyId)`、`findTreeMemberFixture(genealogyId, personId)` 取得含亲属数组深拷贝的快照。成员身份必须由家谱与成员复合定位,缺失、未知或跨家谱身份失败关闭;只有 T04 的明确首位成员模式允许没有 `personId`。Task 18 接入真实新图合同后必须原子删除该临时 owner、选择器和旧 `id/parentId` 模型,不保留双读适配层。
|
||||
- N02 默认回 N01;通知业务目标失效、无权限或字段不足时留在 N02 的明确状态,不猜测页面。
|
||||
|
||||
当前 OpenAPI 已确认 `POST /genealogy/app/auth/register` 的成功响应复用 `LoginResult`,其 `LoginVo` 可返回 `token/accessToken/tokenValue`。因此 A04 的正式终点不是“注册后再登录”,而是保存有效会话后直接 `goRoot(G01)`;在真实注册与行为验证尚未接入前,不得把当前占位反馈冒充注册完成。
|
||||
2026-07-22 新线上 OpenAPI 已确认密码登录、短信登录和注册统一返回 `RAppLoginVo`,其 `data` 引用 `AppLoginVo`,唯一会话字段为 `access_token`。因此 A04 的正式终点不是“注册后再登录”,而是保存有效会话后直接 `goRoot(G01)`;受保护旧快照的响应形状不再作为兼容读取路径,在真实注册与行为验证尚未接入前也不得把当前占位反馈冒充注册完成。
|
||||
|
||||
### 20.7 T03 单实例成员轨迹
|
||||
|
||||
- T03 原生页面实例只保留一个。初始成员成功读取后先把轨迹初始化为恰好 `[initialPersonId]`;初始读取失败不建立轨迹。点击亲属只更新页面内 `personId` 和成员轨迹,不再 `navigateTo` 同一路由。
|
||||
- T03 原生页面实例只保留一个。初始路由 `personId` 是不可变的宿主页路由身份,初始成员成功读取后先把轨迹初始化为恰好 `[initialPersonId]`;初始读取失败不建立轨迹。点击亲属只更新页面内活动成员 `personId` 和成员轨迹,不改 URL,也不再 `navigateTo` 同一路由。
|
||||
- 新成员资料成功读取后才写入轨迹;读取失败保留原成员和原轨迹。
|
||||
- 返回先逐项弹出成员轨迹,轨迹结束后才返回 T01、T07、R02 或实际来源。
|
||||
- T05、T08 返回时刷新当前轨迹项,不新增 T03。
|
||||
- 如果 T03 已位于原生栈下方,再次打开同一 `genealogyId` 的 T03 时必须回到该实例,并用目标页允许的类型化一次性操作请求加载目标成员;不得创建第二个 T03。若栈中 T03 属于不同 `genealogyId`,网关返回 `T03_CONTEXT_CONFLICT` 并拒绝压栈,调用方必须先通过根语义切换家谱上下文。
|
||||
- 当前 T05 本地预览以 `goBack()` 返回并保留原 T03 宿主页身份、活动成员和轨迹,T08 普通返回也不新增 T03。未来真实成员更新结果只允许刷新与 `entityId` 相同的当前活动成员,不得切换轨迹或改写宿主页身份。
|
||||
- 如果 T03 已位于原生栈下方,再次打开同一 `genealogyId` 的 T03 时必须回到该实例,并用目标页允许的类型化一次性操作请求加载目标成员;不得创建第二个 T03。若栈中 T03 属于不同 `genealogyId`,网关返回 `T03_CONTEXT_CONFLICT` 并拒绝压栈;若迁移前遗留栈已有多个 T03,则返回 `T03_STACK_CONFLICT`,不得只激活最近实例后宣称栈已唯一。调用方必须先返回或通过根语义清理冲突上下文。
|
||||
- 从直接链接进入 T03 且没有来源页时,规范父页为带当前成员定位参数的 T01;缺少 `genealogyId` 时继续回退 G01。
|
||||
|
||||
### 20.8 通知和外部进入
|
||||
@@ -805,6 +809,10 @@ PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relatio
|
||||
- 现有人物搜索只需稳定返回字符串 `personId`;新增人物、编辑人物和编辑关系接口必须加入版本并发合同。当前 OpenAPI 没有 T06 所需的关系修改入口,因此新增上述关系 PATCH。不可变 `relationshipKind` 只允许 `PARTNER/PARENT_CHILD`,必须与服务端既有关系一致;请求使用以该字段为 discriminator 的 `oneOf`:PARTNER 分支只更新 `relationType/status`,PARENT_CHILD 分支只更新 `relationType/parentRole`。每个分支除 `relationshipKind` 外至少包含一个可修改字段,省略字段保持原值;只有 discriminator 的空更新返回 `422 RELATIONSHIP_PATCH_EMPTY`。参与人和 `relationshipKind` 不可在 PATCH 中偷换,也不以客户端删除再新增模拟修改。
|
||||
- 旧 v1 `/genealogy/app/genealogies/{genealogyId}/lineage/tree` 保持原合同供既有消费者使用;当前 App 只接入上述四条明确的 `/genealogy/app/v2/...` 路径,不双读、不做运行时版本探测。
|
||||
|
||||
四条操作统一声明 `200/400/401/403/404/422/429/5XX`;tree、overview 和 relationship PATCH 还必须声明 `409`。稳定业务码字段唯一固定为错误响应根层必填字符串 `businessCode`,对应 HTTP 响应使用 `oneOf` 分支中的单值 enum;OpenAPI 3.1 也可使用 `const`。description、example 或无关 metadata 中出现同名文本都不构成合同。`generationRange` 固定为关闭额外字段的 `{ minGeneration, maxGeneration }`,两项均为大于等于 1 的整数;`minGeneration <= maxGeneration` 由运行时校验。
|
||||
|
||||
接口门禁分为两层,不能互相冒充:`tests/lineage-openapi-contract.ps1` 的静态层只验证 OpenAPI 能结构化表达的路径、参数全集与单字段边界、响应引用闭包、discriminator/oneOf、精确字段集、枚举、字符串 ID、错误响应、`If-Match`、JSON/YAML 引用一致性以及 v1 隔离。平铺 query Parameter Objects 即使在 OpenAPI 3.1 中也不能证明 FOCUS/BOUNDARY 的跨参数互斥;入口集合、引用、环、bucket 总和、隐私根、locator 分段首尾、cursor/version 绑定、409/422 真实行为及 PATCH 省略字段保持原值同样必须由运行时 validator 与部署集成测试验证,禁止用 description、example 或关键词命中制造假绿。支持 schema 的文件名不由客户端另造,固定根模型之外沿真实 `$ref` 闭包验证结构;YAML 静态层只证明限定块内的路径、operationId 和完整 component 引用闭包与 JSON 一致,字段语义仍必须在后端同版本导出后逐项复核。
|
||||
|
||||
### 21.10 明确删除的旧路径
|
||||
|
||||
同一迁移中删除:页面内置运行时 members、递归 `children/spouses`、单一 `parentId`、`fatherId/motherId` 与 `parentId` 双读、`person.id || person.personId`、API 或 fixture 的 `x/y`、数字 int64 ID、默认“族人/主支”语义兜底、顶层 `.map(toTreeNode)`、全量 CSS Grid track、多 `<view>` 拼线、Canvas 线叠 DOM 节点、选中态改尺寸和全局 `NODE_HALF_HEIGHT`。
|
||||
@@ -830,14 +838,202 @@ PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relatio
|
||||
|
||||
性能门槛:单窗口不超过 500 人;`dataReady` 定义为规范图完成校验的时间点,`interactive` 定义为首帧绘制结束且命中索引可用,二者间隔不超过 800ms;输入到反馈 p95 不超过 100ms;持续手势帧耗时 p95 不超过 32ms;不连续出现两帧超过 100ms;20 次聚合跳转与搜索后驻留内存相对稳定态增长不超过 15%。每项 MuMu 时延指标至少采样 30 次并按 nearest-rank 计算 p95;手势帧由 renderjs 的 `requestAnimationFrame` 记录,输入延迟从视图层触摸时间戳量到下一完成帧;内存先预热 3 轮,再在相同空闲点比较 20 轮。不把全 App 冷启动混进 T01 门槛。未达到门槛时缩小窗口或进入列表,不能提高上限掩盖问题。
|
||||
|
||||
## 二十二、当前精确执行顺序
|
||||
## 二十二、认证、TAC 与可访问安全设计
|
||||
|
||||
### 22.1 唯一所有者
|
||||
|
||||
- `utils/auth-verification.js` 唯一拥有 `APP_SMS_LOGIN/APP_REGISTER/APP_FORGOT_PASSWORD` 三个认证场景、4 位短信码和 require/verify 响应边界。
|
||||
- `components/TacVerification.vue` 唯一拥有 TAC 浮层、renderjs 加载、可见生命周期、焦点和返回行为;页面不得直接操作供应商全局对象。
|
||||
- `static/tac/js/jiapu-tac-adapter.js` 唯一拥有 TianAi challenge、proof 与 verify payload 映射。后端提供的 `tac.css/tac.min.js/icon.png/dun.jpeg` 保持原字节,由专项哈希合同保护。
|
||||
- `utils/api.js` 唯一拥有已审查请求的 HTTP 200 严格 envelope、15 秒超时、RequestTask 中止和错误分类,并拥有认证密码摘要、`validToken` 发码、`AppLoginVo.access_token` 会话写入及反馈 wire payload。页面不复制 header、地址、响应兼容、token 读取或请求控制器。
|
||||
|
||||
### 22.2 页面与状态机
|
||||
|
||||
A01 默认短信登录;密码登录因为服务端请求体无法消费 TAC 票据而保持可见但不可用,微信登录也不得以视觉入口冒充已接通。A01/A04/A05 的发码顺序固定为:本地字段和协议校验 → `/captcha/require` → 严格 TAC challenge/verify → 取得服务端 `validToken` → `/genealogy/app/auth/sms/code`。验证码精确 4 位,注册或重设提交只消费短信码,不再重复 TAC。手机号变化、刷新 challenge、切换验证方法、返回或卸载都使旧上下文失效;超时、空响应、非 JSON、重复回调和迟到回调失败关闭并保留表单。短信发送与找回密码返回 `RVoid`,其 schema 未把 `data` 列为必填;客户端仍强制 HTTP 200 和整数成功 `code`,但把省略 `data` 与 `data:null` 都归一为 `null`,不把这一合法空响应误判为失败。有实体的 challenge、登录和注册响应继续要求 `data`。
|
||||
|
||||
短信状态覆盖可发送、验证中、发送中、60 秒倒计时、失败重试、到期、手机号变更和重复点击。客户端倒计时不是服务端限流证据;最终仍须用真实服务验证手机号/IP/设备/租户限流、前后台恢复、过期和并发重放。注册成功只读取 `RAppLoginVo → AppLoginVo.access_token`,保存会话后直接进入 G01;找回成功返回 A01,不自动登录。
|
||||
|
||||
### 22.3 后端验证中心合同
|
||||
|
||||
`VerificationCenter` 是唯一授权 owner,供应商只提供 evidence,不直接签发或消费短信票据。`/captcha/require` 建立绑定 tenant、client、scene、规范化 subject 和风险策略版本的 `verificationSessionId`;`required=false` 也直接签发供指定 audience 使用的一次性 grant,不能让短信端点出现无票据分支。同一 session 只允许一个活动 challenge;刷新或切换方法使旧 challenge 失效但不清零累计失败。
|
||||
|
||||
`/captcha/verify` 的请求以 evidence/provider 为 discriminator 使用 `oneOf`,根和每个 payload 都 `additionalProperties:false`。provider、type、scene 和 subject 以服务端 challenge 记录为真,客户端字段不能改变绑定。只有供应商 evidence 与本地风险策略共同通过才签发 opaque `validToken`;票据绑定 session、tenant、client、scene、subject、challenge、method、assurance、audience 和过期时间。`/sms/code` 在同一事务中完成 `ISSUED → CONSUMED` 与唯一短信 outbox 创建;同一幂等键返回原结果,不同键或并发重放不能产生第二条短信任务。
|
||||
|
||||
当前后端门禁固定为:`API-AUTH-TAC-001` 补齐密码登录票据;`API-AUTH-TAC-002` 修复真实 challenge 空 500 与错误 envelope;`API-AUTH-TAC-003` 关闭验证 schema 并建立 discriminator;`API-AUTH-TAC-004` 落实 session、多方法、`required=false` 票据和原子消费。`AUTH-TAC-OPENAPI-CONTRACT BLOCKED` 解除前,`runtimeConfig.mode` 必须保持 `mock`。
|
||||
|
||||
### 22.4 不降风控的可访问路径
|
||||
|
||||
TianAi 指针滑块不能因外层 dialog 可聚焦就被宣称为 TalkBack 或键盘可完成;不得生成固定键盘轨迹,也不得检测辅助技术后免验证。不存在 `accessibility=true`、`skipCaptcha`、供应商故障放行或客服直接发短信等旁路。
|
||||
|
||||
三人交叉质询后的统一 P0 是 provider-neutral 验证中心与可恢复的文字/中继 `MANUAL_REVIEW`。文字渠道只是可访问通信媒介,各 scene 仍有独立身份或号码控制证据。案件继承且不可改写原 session 的 subject/scene,具备去重、RBAC、主体/IP/设备/审核员限额、完整审计、服务时段、容量和 SLA;高风险找回或换号双人复核。坐席只提交决定,验证中心才可签票;异步案件和批准授权在合理期限内可恢复,用户重新进入原流程时才激活短时 token,避免在通知前过期,也不得要求残障证明。
|
||||
|
||||
中国大陆非交互风控供应商只进入限时 POC;必须在真实 UniApp Android WebView 中证明 TalkBack、外接键盘、Switch Access、弱网、超时、异常/重放票据、误杀和攻击拦截指标,达标后才可成为默认自动路径,不能预先宣称符合无障碍标准。音频验证码只作为另一个 POC 候选,必须验证可懂度、听障覆盖与 ASR 对抗,不能单独上线或成为唯一替代。设备断言只有在同一 subject 的已认证会话绑定硬件保护私钥、服务端 nonce、RP/App 绑定、`userVerification=required`、短时单次、防重放并检查撤销时才可独立放行;普通设备指纹、完整性或仅 user-presence 只能作为风险信号,注册和未绑定设备不得使用。
|
||||
|
||||
客户端现有壳层只完成 dialog 命名、说明关联、初始聚焦、Tab 圈定、Escape/Android 返回、焦点恢复、原生刷新/关闭、48px 目标和小视口滚动。最终必须在 MuMu 用 TalkBack、外接键盘与非拖动路径完成 A01/A04/A05;证据缺失时 `ANDROID-AUTH-ACCESSIBILITY-RELEASE BLOCKED` 必须保持红灯。
|
||||
|
||||
## 二十三、领域上下文与反馈提交设计
|
||||
|
||||
`utils/genealogy-context.js` 是当前家谱词法 ID 与失效 tombstone 的唯一持久 owner,`utils/session.js` 是账号令牌边界。只在首次且没有历史上下文或失效标记时自动选择首个可用家谱;调用方给定的新列表不再包含历史 ID,或显式目标不可用时,必须清理 ID、写入 tombstone 并让用户明确重选,后续重载不能用列表第一项继续渲染。令牌损坏、退出和账号切换都同步清理 ID 与标记;导航一次性结果不得承担领域持久化。实际后台撤权仍需 workspace 在 onShow 或失效事件中重新取得服务端列表,当前基础不能被表述为实时权限刷新已完成。
|
||||
|
||||
M07 的唯一接口 owner 是 `appApi.submitFeedback`。请求只允许 `feedbackContent/feedbackType/contactInfo` 三个普通字符串数据字段;内容必填,类型和联系方式可选且无客户端枚举映射;请求选项不能覆盖认证要求。remote 只接受 `POST /genealogy/app/feedback` 的 HTTP 200 与整数成功 `code`,反馈响应未声明 `data` 必填且页面不消费实体;mock 必须抛 `WRITE_UNAVAILABLE`。页面提交中锁定表单并捕获规范快照;成功和 uncertain 回流都再次核对当前快照,保留回执或未知结果并阻止原样重复写,迟到输入保持为尚未提交的新内容。超时、断网、意外 2xx/3xx、HTTP 408/5xx 或响应失真进入 `uncertain`,只有确定拒绝允许重试。本地不持久保存反馈原文或哈希 tombstone;跨重启 at-most-once 必须由未来服务端幂等键或状态查询合同解决,不能以隐私数据、无期限锁或伪保证替代。
|
||||
|
||||
### 23.1 家谱工作区只读合同
|
||||
|
||||
家谱工作区第一批唯一选择 `GET /genealogy/app/genealogies/mine` 持有当前账号可访问集合,选择 `GET /genealogy/app/genealogies/{genealogyId}/overview` 持有 G05 展示;语义重复的 `GET /{genealogyId}` 不同时接入。页面不把 fixture 与远端事实混用,也不在两个详情响应之间择优补字段。
|
||||
|
||||
线上 `AppGenealogyVo.genealogyId` 当前是 JSON `integer/int64`。最大合法 int64 经 JavaScript JSON 解析后会不可逆失真,事后 `String()` 无法恢复,因此响应身份必须改为非空词法字符串;URL path 在 wire 上本是文本,手写客户端可保持词法字符串,不机械把 path 声明本身当成同一阻塞。仅拒绝 unsafe number 可以失败关闭,却会使 OpenAPI 合法用户永久不可用,只能用于关闭功能开关的预研,不能作为正式兼容方案。
|
||||
|
||||
首批最小实体只消费 `genealogyId/genealogyName/canView/canManage/canEditContent/roleType`。两层响应 envelope 的 `code/data` 与这些实体字段必须进入 `required`;ID、名称为非空字符串,三项 capability 为布尔值,`roleType` 提供至少两个稳定非空枚举供 G01 分组和标签。其他字段保持可选:地点、堂号、人数、简介等有值才展示;未知额外响应字段允许忽略。若产品坚持保留当前 fixture 的来源、管理者、认证、始祖、上级谱、支系、更新时间和激活人数,则后端必须另补合同;首批不为复刻 mock 强制 22 个字段全部必填。
|
||||
|
||||
`canView` 是进入 `availableIds` 的授权投影,不能从 `roleType/status/memberStatus` 猜值;后端也可以改为能够由自动化证明“`/mine` 只返回当前仍可查看 ACTIVE 成员”的等价合同。G01 仅在新列表成功校验后 reconcile;网络、超时、5xx 和畸形响应保留旧现场并显示错误,明确无权、撤权或不存在才写 tombstone。G05 每次加载先清空旧数据、取消迟到请求,只消费 `/overview`;能力缺失按最低权限处理,不回退本地角色。
|
||||
|
||||
当前线上 OpenAPI 没有有效 security 声明且媒体写作 `*/*`,但无令牌实测三条读取均被拒绝、实际 Content-Type 为 JSON,因此两项是必须修复的发布文档质量问题,不单独声称已经公开泄漏。真正发布门禁还包括有效令牌下的无凭证、跨账号、撤权、删除、401/403/404/5xx、畸形 JSON 与取消反例。当前部署使用 HTTP 200+业务 `code=401`;后端可以保留业务码承载或改为规范 HTTP 状态,但文档、运行时 validator 与部署行为必须一致。
|
||||
|
||||
`tests/genealogy-workspace-openapi-contract.ps1` 只读取受保护的同版本 JSON/YAML,固定上述响应身份、typed envelope、required 与 capability/role 边界;当前旧双导出与线上文档都不能通过。门禁关闭前不建立 G01/G05 专属字段 adapter,不让客户端成为第二份猜测合同。问题固定为 `API-GENEALOGY-WORKSPACE-001` 无损身份与最小 schema 闭包、`API-GENEALOGY-WORKSPACE-002` 可访问集合与能力投影、`API-GENEALOGY-WORKSPACE-003` 错误语义及对象级授权行为。
|
||||
|
||||
### 23.2 M06 帮助内容只读合同
|
||||
|
||||
M06 第一批唯一选择 `GET /genealogy/app/help-articles` 持有完整 FAQ 列表。线上 `HelpArticleVo` 已同时包含 `helpCategory/helpTitle/helpContent`,所以单页手风琴不再调用 `/{helpId}`;详情端点只保留为未来文章深链或其他 surface 的候选,不进入当前页面。这样当前页面不读取、比较、缓存、序列化或回传 `helpId`,线上 JSON `integer/int64` 虽仍是未来详情债务,却不会参与 M06 身份或行为。
|
||||
|
||||
成功响应必须由 `RListHelpArticleVo` 唯一拥有,并把 `code/data` 设为 required;`data` 是允许为空的 `HelpArticleVo[]`。每行只要求 required、非空的 `helpCategory/helpTitle/helpContent`:分类是可直接展示的标签,正文首版明确为纯文本,数组只含当前可发布文章且顺序就是展示顺序。分类 enum、详情 ID、封面、浏览量、状态和排序字段都不由客户端消费;“全部”是唯一客户端分类值。富文本、Markdown、图片和文章详情只有在另立格式与内容安全合同时才可进入。
|
||||
|
||||
adapter 必须使用 allowlist 投影为 `{category,title,content}`,不得 spread 原对象。每次合法响应先按原始顺序分配仅限当前 generation 的 ordinal 展示键,再进行搜索和分类;筛选后的 index 绝不能成为 key。搜索、分类、刷新或原子替换列表前清空展开项;唯一列表 controller 与 generation 共同拒绝取消后的迟到响应。重复标题或正文不会产生 key 冲突,也不能据此生成持久身份。
|
||||
|
||||
页面状态固定为加载、正常、服务端空列表、搜索/筛选无结果、失败可重试和认证失效;失败不得回退成本地五条内容并冒充线上成功。搜索只匹配标题与纯文本正文,分类从返回标签按首次出现顺序动态去重。分类与标题使用原生按钮语义并满足至少 44dp 触控目标;分类补 `aria-pressed`,问题补 `aria-expanded/aria-controls`,加载、计数、失败和展开状态提供适当播报。最终系统字号、TalkBack、焦点和视觉只能在 MuMu 验收。
|
||||
|
||||
当前受保护 JSON/YAML 仍把列表写成通用 `ListResult/RList`;线上虽已出现 `RListHelpArticleVo/HelpArticleVo`,两者仍无 `required`,正文格式、仅发布与排序保证也未定义。匿名实测列表、带分类列表和详情均为 HTTP 200+`{code:401,data:null}`,而线上文档声明 HTTP 401 string 且 operation 没有有效 security。后端可以选择规范 HTTP 401 或稳定业务 401,但文档、部署和 validator 必须一致。`tests/help-center-openapi-contract.ps1` 当前输出 `HELP-CENTER-OPENAPI-CONTRACT BLOCKED`;问题固定为 `API-M06-001` 专用响应与最小 required 闭包、`API-M06-002` 纯文本/发布范围/顺序语义、`API-M06-003` 认证与错误承载一致性。门禁通过前不写 live-only adapter。
|
||||
|
||||
### 23.3 个人资料只读合同
|
||||
|
||||
`GET /genealogy/app/auth/profile` 是 M01 身份卡、M02 表单初值和 M03 绑定手机号展示的唯一 wire owner。首批不消费 `userId/avatar/status/tenantId/userNo/sex/birthday/registerSource/loginIp/loginDate/clientKey/deviceType`;数字 ID 与状态字典因此不构成本批门禁。M01 当前显示的“创建者”是家谱级角色,不属于账号 profile,必须删除而不是向后端索要错误字段。
|
||||
|
||||
成功响应固定为 `RAppProfileVo → AppProfileVo`,envelope 的 `code/data` required。wire 实体仅把 `phone` 设为 required,并要求 canonical `^1[3-9]\d{9}$`;`nickName/realName/email` 可选,省略是唯一“未设置”形态,出现则拒绝 null、空串和边界空白,姓名为 1—30 字符,邮箱为有效格式且 1—100 字符。把四项全部 required 会让没有实名或邮箱的合法旧账号拖垮 M01/M03;省略可选字段后由 adapter 统一为空串,不是旧字段兼容或双模型。GET 只定义读取;未来 PUT 必须另证省略字段是否保持原值并只发送 dirty fields,不能在本批预判。
|
||||
|
||||
唯一 profile normalizer 在 mock 与 remote 中输出固定 `{maskedPhone,phoneAccessibleLabel,nickName,realName,email}`。原始手机号只在函数局部完成格式校验和掩码,页面、缓存、错误详情、日志与路由不得出现明文;可选字段只有真正缺席才规范成空串,出现但非法时整份响应失败。所有其他响应字段使用 allowlist 丢弃。`appApi.getProfile` 必须经 `resolveRuntimeMode()` 区分 mock/remote;非法配置失败关闭,remote 走严格 envelope、15 秒超时和 request controller,不以 `hasRemoteConfig() ? remote : fixture` 把错误配置伪装成本地数据。三个页面可以各自读取同一 adapter,但不建立跨会话缓存。
|
||||
|
||||
M01 在 `onShow` 刷新并以 controller+generation 拒绝迟到结果;资料 loading/error 只替换身份卡,服务菜单和底栏不因普通读取失败而消失。旧 `query.state=error` 与“重新查看即 ready”是假状态,接线时删除;通知未读数在通知域接通前不得与真实 profile 混装为线上事实。M02 首次加载成功后再填充三个字段并重设 baseline,加载不能制造脏表单;普通 GET 失败可重试,编辑期间不后台刷新覆盖输入,保存仍明确是本地校验,不能冒充 PUT。M03 只让手机号行局部加载/失败,密码入口不依赖 profile;M05 的当前手机号展示在真正 remote 前也必须消费同一掩码 owner 或隐藏,不能保留 fixture。
|
||||
|
||||
账号失效交给会话 owner,普通 network/timeout/5xx/畸形响应只产生可重试读取失败,没有写请求的 uncertain 状态。实施测试必须证明 optional 缺席、出现非法值、明文不泄漏、超大 `userId/avatar` 丢弃、错误配置、取消、迟到回调、M01/M03 局部失败和 M02 baseline。交互同时改为原生按钮,补加载播报、M02 字段错误关联与聚焦、44dp 目标、长昵称换行和装饰图隐藏;最终系统字号、TalkBack、纹理对比和返回流程仍只由 MuMu 验收。
|
||||
|
||||
当前受保护双导出仍引用通用 `ObjectResult/RObject`;线上 `RAppProfileVo/AppProfileVo` 没有 required、phone pattern 或姓名/邮箱边界,GET operation 也缺有效 security/clientid。匿名真实响应是 HTTP 200+业务 `code=401`,文档却列 HTTP 401 string。`tests/profile-openapi-contract.ps1` 当前输出 `PROFILE-OPENAPI-CONTRACT BLOCKED`;问题固定为 `API-PROFILE-READ-001` 专用响应与最小字段闭包、`API-PROFILE-READ-002` 可选字段与隐私投影、`API-PROFILE-READ-003` 认证/媒体/错误一致性。门禁通过前不写 live-only adapter。
|
||||
|
||||
### 23.4 通知读取与已读状态合同
|
||||
|
||||
通知必须分成读取批次和写入批次,不能为了让页面看起来完整而在同一个本地 clone 中伪造读写闭环。读取只由不带 `readStatus` 筛选的 `GET /genealogy/app/notifications` 与 `GET /genealogy/app/notifications/unread-count` 持有;前者返回当前账号完整活动通知集合,集合最多 200 条、按最新优先,后者精确统计同一集合中 `readStatus=UNREAD` 的条目。两个独立请求之间若有新消息或多端读状态变化,短暂不相等是合法并发结果,页面分别展示成功响应并在下一次刷新收敛。
|
||||
|
||||
读取成功的最小 wire 形状为 `RListNotificationVo → NotificationVo[]` 与 `RNotificationUnreadCount → int32`。两层 `code/data` required;计数范围为 0—200。通知首批只 required `noticeTitle/noticeContent/publishTime/readStatus`:标题 1—50,正文 1—1000、完整且为 plain text,时间为带时区的 RFC3339,状态精确枚举 `READ/UNREAD`。客户端不解释 HTML、Markdown、服务端 URL 或 `bizType`,也不从标题、类型或摘要猜业务目标。N01 只将摘要显示限制在最多 160 个 Unicode 字素,N02 必须持有同一响应中的未截断正文。
|
||||
|
||||
读取 adapter 首版公开模型固定为 `{snapshotKey,title,content,publishedAt,unread}`。`snapshotKey` 使用成功响应 generation 与映射前 ordinal 组成的词法 key;裸 ordinal、数组筛选后 index、服务端 int64 ID 和内容哈希都不能成为页面身份。唯一内存快照不可变且不落盘;成功刷新原子替换 generation,退出或账号切换立即清空。N02 在进入时解析并持有该 generation 的完整条目,应用重启、旧 generation、直接构造或未知 key 统一显示“请返回消息中心重新打开”,不能虚构“服务端已删除/已撤回”。
|
||||
|
||||
读取批次显式丢弃 `notificationId/genealogyId/senderUserId/senderPhone/bizId/bizType/noticeType` 等服务端字段;N01 的通用审核 CTA、空态 G10 按钮和 N02 目标按钮一并删除。只有后端以后提供闭合的 `bizType → route key+必填词法参数+权限/失效语义` 字典,并为每种目标给出越权、删除和跨谱反例,才能另立目标分流批次;任何原始 URL 都不得执行。M01 与 G01 共同消费未读数 owner,文案只称“未读消息”,可见数大于 99 时显示 `99+`,可访问名称仍包含真实数量;加载或错误只影响各自消息入口,不锁死根页其他功能。
|
||||
|
||||
写入批次由 `POST /genealogy/app/notifications/{notificationId}/read` 与 `POST /genealogy/app/notifications/read-all` 唯一持有。此时唯一 notification controller 可以从同一个严格列表响应私有保留 `notificationId`,但页面模型、路由、日志和持久缓存仍只见 `snapshotKey`;ID 必须在列表实体与 path 中同为 1—128 位 URL-safe opaque string,禁止 int64 经 JavaScript 解析后再转字符串。该迁移必须原子删除首版“完全丢弃 ID”的内部实现和 N01/N02/M01/G01 所有 fixture 计数与本地 clone 写入口,不保留双 owner。
|
||||
|
||||
单条已读和全部已读对当前账号必须幂等,重复调用成功且无重复副作用。认证失效返回稳定 401;不存在与跨账号单条 ID 统一为 404 `NOTIFICATION_NOT_AVAILABLE`,避免泄露实体存在性。read-all 的截止点是服务端接收该请求时当前账号已经存在的活动通知,截止点之后并发到达的消息保持未读;成功后客户端重取列表和未读数,不用本地递减猜结果。超时、断网、HTTP 408/5xx 或畸形响应属于结果未知,依靠幂等重试或重新读取收敛;明确 4xx 才是确定拒绝。
|
||||
|
||||
当前受保护双导出把列表返回写成通用 `ListResult/RList`,完全缺少 unread-count 路径与专用通知模型;线上虽出现专用 VO 和四条操作,但 wrapper/VO 无 required,`readStatus` 无枚举、内容无长度/纯文本/完整性、列表无容量和顺序,ID 仍是 int64,operation 也缺有效 security/clientid。匿名 list/count 实测均为 HTTP 200+业务 `code=401`,与文档 HTTP 401 string 冲突。`tests/notification-read-openapi-contract.ps1` 和 `tests/notification-read-state-openapi-contract.ps1` 当前分别输出 `NOTIFICATION-READ-OPENAPI-CONTRACT BLOCKED` 与 `NOTIFICATION-READ-STATE-OPENAPI-CONTRACT BLOCKED`;问题固定为 `API-NOTIFICATION-READ-001`—`003` 和 `API-NOTIFICATION-STATE-001`—`003`。双门禁通过前不写 live-only adapter,也不把本地已读行为称为成功。
|
||||
|
||||
### 23.5 M02 个人资料写入合同
|
||||
|
||||
M02 继续只使用 `PUT /genealogy/app/auth/profile`,不同时发布 PATCH 或第二写入口。由于页面只拥有 `nickName/realName/email`,PUT 必须在 operation 与专用 `AppProfileMergeUpdateBody` 中明确是原子 dirty-only merge,而不是整个 profile replacement:只允许出现 1—3 个真正改变的可编辑字段,出现字段更新、省略字段保持,额外字段关闭;同一 payload 重复设置相同值不能产生重复通知或其他业务副作用。后端若无法证明 presence-aware merge,就必须先替换现有操作,客户端不能从“字段 optional”推断安全。
|
||||
|
||||
`nickName` 出现时必须是去边界空白的 1—30 字符,空串和 null 均非法;这允许没有昵称的旧账号只修改其他字段,但不允许把已有昵称删除。`realName/email` 的精确空串只在 request command 中表示清空,省略仍表示保持;非空值分别满足 1—30 和 email 格式/1—100。null、纯空白、边界空白都非法,服务端不以隐式 trim 把空白偷偷解释成 clear。GET 和成功响应中,清空后的可选字段继续以属性省略表达,不能把请求命令形状泄漏回读取模型。
|
||||
|
||||
并发只使用一个 opaque `profileVersion` owner。`AppProfileVo.profileVersion` required,值为 1—128 位 URL-safe string;PUT required `If-Match` 携带同一 token,请求 body 不重复版本。后端以当前 principal+tenant+version 原子 compare-and-set,成功 200 返回完整 canonical `RAppProfileVo` 与新版本;旧版本返回 409,唯一稳定码固定为 `PROFILE_VERSION_CHANGED`。本项目 T01 已使用 body version+If-Match+409 模式,资料写入沿用同一并发语义;H5 CORS 必须允许 `If-Match`。
|
||||
|
||||
客户端先依赖 23.3 的 GET 取得 canonical 初值和版本,完成异步回填后建立 baseline。`normalizeProfileUpdate` 只从普通自有数据属性提取脏字段,禁止 getter、symbol、prototype、avatar/sex/birthday 和额外属性;clean 不发请求。提交开始时冻结三个输入并捕获规范快照、版本与 session generation;成功只接受严格 HTTP 200 typed envelope,以返回模型原子回填表单和 baseline。确定的 400/401/409/422 保留草稿并进入对应状态,409 不静默覆盖。
|
||||
|
||||
超时、断网、408/5xx、取消或畸形成功响应都是 outcome unknown。客户端不得自动重复 PUT,而先重新 GET:若本次所有脏字段均等于提交值,视为已提交;仍等于旧 baseline 才可由用户重试;出现第三值或版本无法归因则进入 conflict,并保留当前草稿供用户明确选择重新载入或基于最新值继续。页面卸载、退出和账号切换中止等待并清空未持久草稿;RequestTask 取消不证明服务端没有落库,旧 session generation 的迟到响应永远不能污染新账号。
|
||||
|
||||
M02 当前把 `currentUser.name` 同时填入昵称和真实姓名,是必须删除的 PII 伪造;500ms 定时器只形成本地预览,不是接口状态。真正实施时状态至少包括 loading、ready/dirty、saving、success、error、uncertain、conflict、auth-expired;保存期间锁定输入和返回,成功重置 dirty,失败保留草稿。头像假按钮、相册权限和 int64 avatar 不混入本批;“邮箱用于接收通知”在验证/送达合同缺失时删除。原生输入和按钮补齐 `aria-invalid/aria-describedby`、错误关联、首错聚焦、busy/status 播报、email 键盘类型与 44dp 目标,最终只由 MuMu 验收。
|
||||
|
||||
受保护双导出仍使用旧 `ProfileUpdateBody`,缺 realName/email,示例还出现 schema 外 `regionCode/addressDetail`,成功为 generic `RObject`。线上改为 `AppProfileUpdateBody → RAppProfileVo`,但 body 无 required/minProperties/关闭额外字段/merge/version,响应无 required,operation 无 security/clientid,媒体还是 `*/*` 且只列 200/401。`tests/profile-update-openapi-contract.ps1` 当前输出 `PROFILE-UPDATE-OPENAPI-CONTRACT BLOCKED`;问题固定为 `API-PROFILE-UPDATE-001` 唯一 merge owner 与字段命令、`002` 版本并发和 409、`003` typed 响应/认证/错误/CORS、`004` 超时对账与账号隔离。读取和写入门禁都通过前不接 M02 真实保存。
|
||||
|
||||
### 23.6 M10 当前设备退出合同
|
||||
|
||||
`DELETE /genealogy/app/auth/logout` 唯一语义是撤销 Authorization bearer 所属的当前设备 credential family,包括同一登录会话的 refresh 能力(如果未来存在);同账号其他设备保持有效。“退出全部设备”必须是另一条接口、另一层确认和另一份权限合同。200 只在撤销已经传播到所有鉴权节点、旧 access token 不能再访问任何受保护接口且旧 refresh 不能换取新 token 时返回;已经在鉴权前通过的并发业务请求无法回滚,产品文案不得承诺取消所有进行中操作。
|
||||
|
||||
DELETE 没有 body,required SaToken 和与 token client 绑定的非空 clientid。为形成唯一幂等成功出口,凡能验证为该 client 历史签发的 active、revoked 或 expired credential,重复调用都返回相同的 HTTP 200 `RVoid`,不产生额外副作用;伪造、格式非法和 client 不匹配才返回 401 `RLogoutRejected`,稳定码只允许 `TOKEN_INVALID/TOKEN_CLIENT_MISMATCH`,它们不是撤销成功。200/401 均为 application/json 且 `Cache-Control: private, no-store`,RVoid 的 integer code required,同时声明 400/429/500。
|
||||
|
||||
客户端唯一 owner 是服务级 `logoutCoordinator`,而不是 M10、A01 和 session 各存一份。一次同步临界段按顺序:捕获 A 的 token/clientid 和当前 session epoch;通过 session owner 立即 bump epoch 并且只清一次 token、家谱上下文与所有已登记账号缓存;使用显式 A 快照创建不绑定页面 controller 的 DELETE RequestTask;发布非敏感 attempt 并立即 `goRoot(A01)`。该同步段没有 await,创建请求同步失败也不恢复 token。M10 页面永远拿不到 token,请求不会因 M10 卸载或 reLaunch 主动 abort。
|
||||
|
||||
异步 callback、catch 和 finally 只能按 attemptId 更新 coordinator,绝不能再次 `session.clear()`;否则 A 的迟到响应会抹掉随后登录的 B。coordinator 的公开内存态固定为 `{attemptId,logoutEpoch,status}`,status 为 `pending/confirmed/unconfirmed/not-revoked`,不含 token、header、响应 payload 或服务端消息。最多允许用同一 A 快照做一次同进程短时有界重试;不写 storage、日志或持久队列,也不从 session 重新取 token。
|
||||
|
||||
A01 仅在 session 仍为空、epoch 与 logoutEpoch 相等时订阅并原子消费一次状态;B 登录或 epoch 改变后丢弃 A 的迟到提示。进程被杀允许丢失提示,但本地退出已经持久完成。唯一主句始终是“已从本机退出”:pending 补“正在结束服务器会话”,严格 200 为 confirmed;network/timeout/408/429/5xx、畸形响应或 generic 401 为 unconfirmed;typed 401/400/403 为 not-revoked。所有分支都留在 A01、不恢复 token、不返回 M10、不阻塞重新登录;状态必须可播报且不能只靠短 toast 或颜色。
|
||||
|
||||
M10 确认前的取消和 Android 返回只关闭确认层,零请求、零清理;确认后立刻退出可关闭弹层态并防双击,根导航失败时显示“本机已退出”遮罩且禁止返回受保护页面。当前文案“本机保存的密码不会被保留”没有任何密码存储 owner 证据,实施时改为“仅退出此设备,其他设备不受影响”。Mock 或错误 runtime config 也必须完成本机退出,并准确标记远端未确认,不能为了发请求把用户困在本地会话。
|
||||
|
||||
当前 M10 只做 `session.clear → close → goRoot(A01)`,本机边界正确但没有远端撤销、重复保护、epoch 或跨根提示;旧静态测试也锁定这条本地序列。受保护双导出有 DELETE、SaToken、clientid 和 200 `RVoid`,但缺范围、幂等、required 和错误;线上只有 200/401 `*/*`,又缺 operation security/clientid。`tests/logout-openapi-contract.ps1` 当前输出 `LOGOUT-OPENAPI-CONTRACT BLOCKED`;问题固定为 `API-LOGOUT-001` 当前凭证族与撤销传播、`API-LOGOUT-002` 幂等成功/拒绝模型、`API-LOGOUT-003` 安全/媒体/no-store/部署反例。门禁通过前不新增 logout API 代码,也不把本机退出写成服务端注销成功。
|
||||
|
||||
### 23.7 M04 登录态改密与统一密码凭证合同
|
||||
|
||||
当前本地双导出要求 `oldPassword/newPassword` 为 32 个十六进制字符的 MD5,线上 `AppPasswordChangeBody` 也只把它放宽为大小写十六进制;登录、注册和找回同样接受静态摘要。该摘要不是一次性 proof,而是后端登录入口直接接受的密码等价物,一旦从日志、代理、调试记录或终端泄漏即可重放;同时服务端看不到原始长度与 blocklist 命中,无法成为生产策略 owner。新合同不得长期双读 raw/MD5,也不得只改 M04 留下其他入口。A01 登录、A04 注册、A05 找回和 M04 改密必须同一批删除全部 MD5 wire fallback;只在认证 HTTPS 通道提交 raw `writeOnly` 密码,服务端使用独立盐与 Argon2id,无法使用时才采用合规 scrypt/PBKDF2。
|
||||
|
||||
密码 schema 只有两个 owner:`CurrentPasswordSecret` 原样、不 trim,允许 1—64 Unicode code point 以兼容已有账号;`NewPasswordSecret` 在明确 NFC 规则后为 15—64 Unicode code point,允许空格、Unicode、粘贴和密码管理器,不设置字母/数字/符号组成规则。注册、找回与改密新值共用后者;登录与改密当前值共用前者。服务端在写入前执行常见/泄露密码 blocklist、账号级限速、当前密码重新认证和新旧不同;客户端只镜像即时提示,不能成为可绕过的权威。TAC 是反机器人证据,不代替当前密码;用户要求的登录/注册/找回 TAC 继续由认证合同持有,正常 M04 不另建一套 TAC。
|
||||
|
||||
M04 的唯一会话方案是 ALL,而不是返回并安装新 token,也不是保留旧 bearer。严格 HTTP 200 `RVoid` 只能在新 verifier 已持久化、账号 `credentialEpoch` 已原子递增,并且包括请求者在内的所有设备、所有 client 的既有 access/refresh/renewal 会话已跨鉴权节点失效后返回。方案 B 会要求 typed 新 token 和响应丢失恢复协议,当前 RVoid 无法承载;方案 C 会让可能被盗的旧 token 在改密后继续有效,均被否决。两个使用同一旧密码的并发请求必须通过服务端 CAS 至多一笔成功,另一笔返回 typed 409 `CREDENTIAL_VERSION_CONFLICT`。
|
||||
|
||||
PUT required SaToken、非空 clientid、`application/json` 和关闭额外字段的 `PasswordChangeBody`;body 只含 oldPassword/newPassword,confirm 永不出端。200/400/401/409/422/429/500 都必须是 JSON 且 `Cache-Control: private, no-store`,429 带 `Retry-After`。409/422 的 `RPasswordChangeRejected.businessCode` 只允许 `CREDENTIAL_VERSION_CONFLICT/CURRENT_PASSWORD_INCORRECT/NEW_PASSWORD_SAME_AS_CURRENT/PASSWORD_POLICY_VIOLATION`;服务端和网关日志、APM、分析、崩溃记录及错误消息不得含密码、摘要或请求体。
|
||||
|
||||
客户端在 dispatch 前由 session owner 持久化唯一非敏感 marker `{sessionEpoch,startedAt}`,不保存 token、密码、摘要、body、重试键或 operation id。明确未写的 400/422/429 原子清 marker 并留页;严格 200、401、409 均清同 epoch 全部账号态并回 A01。network、timeout、取消、畸形 2xx 或 5xx 在发出后属于结果未知,同样清秘密与本机会话、回 A01 并只提示“请尝试使用新密码重新登录”,绝不自动重试或宣称失败。冷启动发现同 epoch marker 时,在任何受保护缓存渲染前先清会话;新登录 bump epoch,旧 marker 和 A 的迟到响应不得清掉 B。无需新增 operation-status,重新登录就是最小对账路径。
|
||||
|
||||
页面实施时状态至少是 ready/submitting/known-error/unknown/success:提交中冻结三个输入、显隐控制、按钮和返回;确定字段错误聚焦并关联 `aria-invalid/aria-describedby`,显隐改为可键盘操作的原生按钮与 `aria-pressed`,目标至少 44dp,持续状态可播报。成功与 unknown 的跨根提示不能只靠短 toast。M03“建议定期更新”改为风险触发建议;M04 允许自动填充、粘贴和密码管理器。`tests/password-change-openapi-contract.ps1` 当前输出 `PASSWORD-CHANGE-OPENAPI-CONTRACT BLOCKED`;`API-PASSWORD-001`—`005`、密码登录 TAC、双设备/并发/故障注入和 MuMu 原生矩阵通过前,不修改 M04 的诚实本地预览。
|
||||
|
||||
### 23.8 M05 手机号换绑与统一短信码合同
|
||||
|
||||
M05 当前只展示脱敏 fixture,以 500ms 定时器验证新手机号和 4 位验证码并明确“不提交服务器”;没有当前密码、TAC、远端请求、会话迁移或结果未知状态。受保护双导出的 PUT body 是 `clientId/phone/smsCode`,线上则只有 `phone/smsCode` 并返回完整 profile;两者都缺 existing-factor 再认证、号码唯一性、会话撤销和 typed 错误。共享发码接口在线上还明确忽略权限。活动 bearer 加攻击者控制的新号验证码因此足以构成账号接管,不能接线。
|
||||
|
||||
三人比较了“共享发码 operation 按 scene 条件鉴权”和“专用受保护 operation”。标准 OpenAPI 3.x 无法把 operation-level security 与 body 中的 `sceneCode` 条件绑定;`security: [{}, {SaToken: []}]`、文字说明或 vendor extension 都不能让通用 validator、网关和 SDK 自动拒绝匿名 `APP_PHONE_CHANGE`,会产生 validator 允许而 runtime 拒绝的双合同。唯一方案是 `POST /genealogy/app/auth/phone/sms/code`:required SaToken 与非空 clientid,闭合 body 只有 `phone/validToken`,scene 在服务端固定,公共 `/auth/sms/code` 的 enum 删除 `APP_PHONE_CHANGE`。两个 operation 只分协议边界,底层仍调用同一 OTP 生成、存储和限流 owner,不复制策略。
|
||||
|
||||
最终 PUT 的身份闭环是活动 session+raw `currentPassword`+新号 OTP。TAC 只防自动化,不能替代既有因子;新号 OTP 只证明新号码控制权。不强制旧号 OTP,因为旧号丢失是正常换绑原因且不会增加独立因子;成功事务改为持久化旧号安全通知 outbox。所有账号若不保证有密码,稳定返回 `STEP_UP_UNAVAILABLE` 并进入独立找回,M05 不得降级为 bearer+新号 OTP。最终 body 精确为 `currentPassword/phone/smsCode`,不含 currentPhone、clientId、validToken、challengeId 或供应商字段;它依赖 M04 先完成 raw-password/慢哈希迁移,当前 MD5 wire 不得复用。
|
||||
|
||||
短信码唯一 schema owner 是 `SmsCodeSecret`:CSPRNG 生成恰好 6 位 ASCII 十进制字符串,保留前导零,TTL 5 分钟,60 秒重发冷却,最多 5 次失败,单次消费;重发原子废止旧码且不重置累计失败计数。同一 `(account,session/credentialEpoch,tenant,client,scene,newPhone)` 只有一个 active generation,服务端内部 generation 足以消歧,因此最终 PUT 不新增 challengeId/attemptId。A01/A04/A05/M05、注销等活动消费者、短信模板、生成器、OpenAPI、validator、页面和测试必须同版本从 4 位原子迁移为 6 位,禁止兼容双长度。
|
||||
|
||||
服务端在同一事务内验证当前密码与 active OTP、执行 `(tenant,canonicalPhone)` 唯一约束、消费 OTP、更新手机号、递增 credentialEpoch、撤销包括当前在内的全部 access/refresh session,并持久化旧号通知 outbox;严格 200 只返回 `RVoid`。两个并发换绑至多一笔成功,另一笔按 credential CAS 无写入。发码 POST 的结果未知不清登录态,但按服务端冷却避免立即轰炸;最终 PUT dispatch 前复用无秘密 `{sessionEpoch,startedAt}` credential marker,200、401、409、network/timeout/5xx/畸形响应或进程终止均清同 epoch 本机会话回 A01且不自动重试。
|
||||
|
||||
M05 页面实施时状态至少为 ready/tac/sending/code-sent/submitting/known-error/unknown:新号变化作废 TAC 与 OTP,发码后锁定号码,提交中冻结全部字段和返回。当前密码支持粘贴、密码管理器和带 `aria-pressed` 的显隐按钮;手机号与 OTP 使用能保留前导零的文本/电话输入和 numeric inputmode,错误具备 `aria-invalid/aria-describedby` 与首错聚焦,发送按钮为至少 44dp 的原生按钮,倒计时和持续状态可播报但不每秒打断 TalkBack。`tests/phone-change-openapi-contract.ps1` 当前输出 `PHONE-CHANGE-OPENAPI-CONTRACT BLOCKED`;`API-PHONE-001`—`005`、M04/认证前置门禁、两设备/OTP/故障注入和 MuMu 矩阵完成前不修改诚实预览。
|
||||
|
||||
### 23.9 G03 家谱与始祖原子创建合同
|
||||
|
||||
G03 当前在一个页面实例中先收集家谱资料,再录入始祖,两个 320ms 定时器只生成 `local-created-*` 预览;没有真实 API、可信 `regionCode`、上下文安装或 G01/G05 远端回流。受保护双导出的 `GenealogyCreateBody` 和线上 `AppGenealogyCreateBody` 都只创建家谱;通用 `/lineage/persons` 又允许客户端提交代数、父母和账号字段。若按旧接口先建谱再建根,会制造非产品需求的 `ROOT_REQUIRED` 半成品及两次结果未知。三人先讨论了两写恢复,再反向检查是否存在跨库或“保存空谱”的硬约束;当前没有任何证据,因此一致选择原子 bootstrap。只有后端以后证明事务边界不可跨越时才重新立 saga,而不是把补偿复杂度预埋客户端。
|
||||
|
||||
唯一写 owner 仍是 `POST /genealogy/app/genealogies`,但旧 `GenealogyCreateBody/AppGenealogyCreateBody` 必须被 `AppGenealogyBootstrapBody` 原子替换,不保留双收。根对象关闭额外字段,只允许 `genealogyName/surname/ancestralHall/regionCode/accessPreset/rootPerson`,前五项除堂号外均必填;`rootPerson` 关闭额外字段,只允许 `name/sex/birthDate/biography` 且 name/sex 必填。服务端固定根人物 `generation=1` 和唯一首根,不接收 `generationName/personNo/appUserId/fatherId/motherId/status`;`sex` 只允许 `MALE/FEMALE/UNKNOWN`,页面默认 UNKNOWN;生日是本地日 `format: date`,不保留页面武断的 1800 年下限。文本按 Unicode code point、NFC 和无边界空白统一验证,长度沿用页面现有 4/24/12/20/200 上限。
|
||||
|
||||
严格 200 前必须在一个事务内完成配额竞争检查、家谱、OWNER 成员关系、唯一一世始祖、READY 状态和幂等成功回执;任一步失败全部回滚,通用人物 POST 只服务 READY 家谱中的普通人物,不能承担 bootstrap。数据库 `bootstrap-root` 标记是根身份唯一权威,不能从 generation=1 猜测:普通人物 POST 即使提交一世也不能新建/替换根;人物 PUT 对根的可编辑白名单精确且只有 `name/sex/birthDate/biography`,operation 以 `x-bootstrap-root-editable-fields` 登记这四项并以 `x-bootstrap-root-noneditable-policy=REJECT_422_BOOTSTRAP_ROOT_IMMUTABLE` 登记拒绝策略;任何 status/personStatus、账号绑定、世代、父母、根标记或其他字段即使出现在通用 body 中也必须以 typed 422 拒绝,不能用“值未变化”掩盖越权字段。人物 DELETE 不能删除根,parents mutation 不能给根新增或重挂父母。未 READY 固定 typed 409 `GENEALOGY_NOT_READY`,触碰白名单外根字段或根身份固定 typed 422 `BOOTSTRAP_ROOT_IMMUTABLE`。成功 `GenealogyBootstrapResult` 必填词法字符串 `genealogyId/rootPersonId`、`setupState=READY`、`roleType=OWNER` 和 `canView=true`;两个 ID 从 JSON 到 storage、context 和 URL 都不得经过 JavaScript Number。同姓、同名和同地区均合法,页面重复提醒只能是建议,后端不得把名称当唯一键。
|
||||
|
||||
访问规则的唯一 wire owner 是共享 `GenealogyAccessPreset`,枚举只有 `MEMBER_ONLY/PUBLIC_APPLY`。这不是 G03 私有别名:同一后端版本必须让 APP 家谱读取 `AppGenealogyVo`、bootstrap 创建和 G11 设置更新都引用它,并删除 `visibility/joinMode`、旧 create/update DTO 和 `utils/genealogy-contracts.js` 的远端数字映射;客户端不得长期同时接受新 enum 与旧 pair。设置请求同步收紧为闭合、至少一个脏字段的 `AppGenealogySettingsUpdateBody`,只含 `genealogyName/intro/accessPreset`。本任务只统一 G11 的字段合同,不代表设置写入已经可上线;G11 的 `If-Match`、版本/CAS、权限刷新和结果未知仍须在独立批次建立门禁。在后端合同尚未落地的当前本地预览阶段,现有 fixture 映射暂不改写,也不能冒充生产 wire。
|
||||
|
||||
所在地只能从 required SaToken、非空 clientid 的 `GET /genealogy/app/region/search` typed `RegionSelectVo` 选择;同版删除旧公共 `/genealogy/region/search`,不能让 APP 在两条 owner 间漂移。`regionCode/label/selectable` 必填,code 使用共享词法 `GenealogyRegionCode`;`leaf` 只表示树导航,不能被客户端猜成“可提交”。页面展示 label、只提交 code,最终 POST 在业务事务中再次验证仍可选;不强制县、乡或 leaf,避免无依据排除省市级、历史地域或要求过细隐私位置。搜索加载、无结果、失败、迟到响应和失效选项分别表达,自由文本不得反推 code。
|
||||
|
||||
POST required SaToken、非空 clientid 和 `Idempotency-Key`。`GenealogyBootstrapOperationKey` 精确为 `gcb.{13位毫秒时间}.{22—43位 base64url 随机量}`,随机量必须来自至少 128 位 CSPRNG;`issuedAt` 从 key 提取,`acceptUntil=issuedAt+10 分钟`,一律以服务端时间判定,issuedAt 晚于 serverNow 5 分钟以上固定 400 `OPERATION_KEY_INVALID`。schema 同时用 `x-issued-at-source=KEY_EPOCH_MILLISECONDS`、`x-accept-window-seconds=600`、`x-max-future-skew-seconds=300` 机器锁定公式。仍在窗口内的首次请求先用短控制事务按 `(account,tenant,client,path,key)` 唯一 CAS 认领 `PENDING`、canonical digest、fencing lease 和 `resolveBy`;`resolveBy<=claimedAt+2 分钟`,并以 `x-resolve-from=CLAIMED_AT/x-resolve-sla-seconds=120` 锁定。相同作用域、key 与 canonical body 的已存在操作即使超过 acceptUntil 也可重放同一结果,不同 digest 固定 409 `IDEMPOTENCY_KEY_REUSED`;截止后仍不存在的 key 固定 409 `OPERATION_KEY_EXPIRED`,不得启动工作。
|
||||
|
||||
控制事务认领后,业务事务才原子执行配额竞争检查、家谱、OWNER、唯一一世始祖、READY 和 `SUCCEEDED` 回执;任一写点失败全部回滚,再以 fencing token CAS 成不可变 `FAILED_NO_COMMIT`。超出 lease/resolveBy 的 watchdog 也只能用同一 CAS 终结,旧 worker 失去 fencing 后不得提交,保证 PENDING 最迟 2 分钟内收敛。严格 200 只表示业务事务与成功回执均已提交;SUCCEEDED 防重记录至少覆盖实体生命周期,FAILED_NO_COMMIT 记录至少保留 30 天。
|
||||
|
||||
为避免把始祖姓名、生日和生平写入 UniApp 普通持久存储,崩溃恢复选择受鉴权 `GET /genealogy/app/genealogy-bootstrap-operations/{operationKey}`,不持久化完整 body。状态响应必须用带显式 mapping 的 discriminator `oneOf` 精确区分 `PENDING{resolveBy,retryAfterSeconds}`、`SUCCEEDED{result}`、`FAILED_NO_COMMIT`,三个 status 均是单值 string;PENDING 的 `retryAfterSeconds` 为 1—30,避免给所有 200 错加 Retry-After。`x-state-transitions` 只登记 `ABSENT→PENDING` 与 `PENDING→SUCCEEDED/FAILED_NO_COMMIT`,两个终态无出边,`x-terminal-immutable=true`;FAILED schema 同时固定 `x-domain-effects=NONE` 与 `x-quota-consumed=false`,机器保证家谱、OWNER、始祖和配额均未提交。SUCCEEDED 返回与 POST 相同严格回执。GET 只允许 operationKey path 与 clientid header、不得有 request body,是纯读且绝不创建墓碑或推进状态:acceptUntil 前无记录返回 typed 404 `BOOTSTRAP_OPERATION_NOT_AVAILABLE`,同时返回服务端 acceptUntil 与 Retry-After;截止后无记录则按 key 时间可计算地返回 FAILED_NO_COMMIT 而不写库,迟到 POST 仍永久拒绝。跨 account、tenant 或 client 查询统一 404且不泄漏存在性,禁止额外 403 分叉。操作记录不进 `/mine`、不占业务配额,也不返回请求 body 或人物 PII;GET 零写入和 FAILED 零领域提交最终仍须数据库观测测试证明,OpenAPI 描述与扩展不能冒充实现证据。
|
||||
|
||||
客户端唯一 coordinator 在最终按钮校验全部字段后才冻结 canonical snapshot、生成 key,并在 dispatch 前持久化 `{sessionEpoch,operationKey,startedAt}`;第一步 CTA 只写“下一步:录入首代”,绝不发网络请求。客户端本地校验失败不写 marker;请求对象创建失败且能证明零发出时清 marker。服务端在 claim 前返回的 400/401/403 均保证无 operation,清 marker,其中 401 还必须清会话并回登录,403 失败关闭。409 `IDEMPOTENCY_KEY_REUSED` 视为合同/篡改冲突,marker 转入 fatal/quarantined,不查询或安装该 key 的 status、不自动换 key;只有用户看到明确警告并显式放弃后才清 marker、重新开始。创建上限、过期 key 和 422 是确定未提交,可清 marker后修正。429 保持 marker、同 key/body,先查 status 再按 Retry-After 重试;500、网络、超时、408、发出后的取消、任意意外 2xx/3xx、畸形 200 或进程终止均是 unknown,保持 marker并查 status,绝不换 key。status 的截止前 404 保持 key,400 表示本地 marker 已损坏并安全清除,401 执行会话失效,429/500、网络、取消、意外状态和畸形 200 均保持并退避;只有 FAILED_NO_COMMIT 或可证明未认领的确定失败才清 marker。
|
||||
|
||||
当前进程 unknown 可用相同 key 与内存 snapshot 重放;进程重启只查状态,不从 `/mine` 按名字猜测,也不自动生成新 key。SUCCEEDED 的唯一次序固定为:持久化 committed receipt → 失效或定点更新 `/mine` 缓存 → 安装 genealogy context → 进入 G05;context 或导航失败只重试本地安装/导航,绝不重发创建。退出、换账号和 epoch 变化必须隔离旧操作;任何日志、路由、marker、遥测和崩溃记录都不得包含表单正文。
|
||||
|
||||
页面实施必须同步删除伪提交 owner 与 mock fixture mutation,覆盖地区加载、确定拒绝、fatal/quarantined、PENDING、结果未知、已提交待进入、上下文失败和导航失败。文本标签与 input 建立关系;访问预设使用真正 radio/`aria-checked`;按钮至少 44dp,提交中冻结字段和返回;错误具备 `aria-invalid/aria-describedby`、首错聚焦和持续状态播报;重复提醒与成功/放弃层复用具备焦点圈定和恢复的 `AppDialog`。当前源码仍有 clickable view、54rpx 目标、23rpx 选项、无关联错误和 UTC 日期上限,这些只登记为实施项;没有 MuMu 证据前不得宣称视觉或 TalkBack 上线。后端门禁先执行无第三方依赖的 `tests/openapi-yaml-json-parity-runtime-smoke.js`,以无损任意精度数字、严格 YAML mapping 分隔符和完整对象结构深比较 JSON/YAML,确保不安全整数差一也不能假绿,再由 `tests/g03-bootstrap-openapi-contract.ps1` 检查唯一 JSON 语义、组合 schema 内旧字段、同 scope 参数重复、根 PUT 精确白名单和结构化不可变终态;客户端门禁 `tests/g03-bootstrap-client-release-gate.ps1` 实际执行依赖注入的 marker/status 状态机测试,不再用 helper 名称顺序冒充行为证据。两项只是 G03 自身门禁;真实开放还必须同时通过家谱工作区读取门禁、聚焦测试全量回归与 MuMu 原生交互/无障碍验收,任一红灯都不得开放真实创建。
|
||||
|
||||
## 二十四、当前精确执行顺序
|
||||
|
||||
后续不再按页面样式迁移重做,而按以下独立阶段执行:
|
||||
|
||||
1. 导航栈语义统一:先测试和共享所有者,再按认证、G、T、F、R、N/M 小批迁移,每批 MuMu 闭环。
|
||||
2. T01 大规模世系树:先向后端提交新图合同,等待 Apifox 更新并验证,再按图合同、布局、Scene、Canvas、页面交互分批实施。
|
||||
3. 短信验证码完整状态机。
|
||||
4. 跨页面领域数据持久化。
|
||||
5. 全局文字层级与无障碍第二轮。
|
||||
1. 导航栈语义统一:静态任务 1—10 已完成,MuMu 流程矩阵待执行。
|
||||
2. T01 大规模世系树:设计与 OpenAPI 红灯已完成;等待后端新图合同后按规范化、布局、Scene、Canvas 和页面交互分批实施。
|
||||
3. TAC 认证:客户端与静态/纯运行时门禁已完成;等待后端关闭 `API-AUTH-TAC-001`—`004`,随后执行真实环境与 Android 发布门禁。
|
||||
4. 领域上下文基础与 M07 真实反馈客户端已完成;家谱工作区三人审查与 OpenAPI 红灯已完成,等待后端关闭 `API-GENEALOGY-WORKSPACE-001`—`003` 后再接 G01/G05。
|
||||
5. G03 原子 bootstrap 三人审查与 OpenAPI 红灯已完成;等待后端关闭 `API-G03-001`—`005` 后,再实现严格 coordinator、无 PII 状态恢复、地区选择、context/G05/G01 闭环和独立 MuMu 矩阵。
|
||||
6. M06 帮助三人审查与 list-only OpenAPI 红灯已完成;等待后端关闭 `API-M06-001`—`003` 后再接严格 adapter。等待期间继续下一个无依赖只读域,不与验证码、T01 或支付混合。
|
||||
7. M01/M02/M03 个人资料读取三人审查与 OpenAPI 红灯已完成;等待后端关闭 `API-PROFILE-READ-001`—`003` 后再接掩码 adapter,不与资料写入混合。
|
||||
8. 通知读取和已读写入已分别完成三人审查与 OpenAPI 红灯;后端先关闭读取合同后实现无 ID 内存快照,再在独立写批次私有迁移字符串 ID、删除伪本地读状态并验证并发收敛。
|
||||
9. M02 资料写入三人审查与 OpenAPI 红灯已完成;等待读取与 `API-PROFILE-UPDATE-001`—`004` 同时关闭后,再分 normalizer、API 和页面状态实现。
|
||||
10. M10 当前设备退出三人审查与 OpenAPI 红灯已完成;等待后端关闭 `API-LOGOUT-001`—`003` 后,再实现 session epoch、logoutCoordinator 和 A01 一次性状态。
|
||||
11. M04 登录态改密与四条密码 wire 三人审查、OpenAPI 红灯已完成;等待后端关闭 `API-PASSWORD-001`—`005` 后,原子迁移共享策略、MD5 消费者、session marker、页面状态机与全设备撤销。
|
||||
12. M05 手机号换绑与全活动 OTP 三人审查、OpenAPI 红灯已完成;等待后端关闭 `API-PHONE-001`—`005` 且 M04/认证前置门禁通过后,再原子迁移六位码、专用受保护发码、最终 PUT 与 credential marker。等待期间转向 G/F/R,不混入本批。
|
||||
13. 全局文字层级与无障碍第二轮。
|
||||
14. 生产配置、隐私权限、可观测性、构建发布、升级回滚与 MuMu 全流程终审。
|
||||
|
||||
T01 必需的线性目录和 44dp 控件随 T01 一起完成;不借此提前改造全项目文字体系。任何阶段完成前都不开始下一阶段的业务代码。
|
||||
T01 必需的线性目录和 44dp 控件随 T01 一起完成;认证浮层的壳层无障碍不扩张为全项目已通过。外部门禁阻塞时继续无依赖批次,但任何真实成功、接口兼容或视觉通过都必须有对应证据。
|
||||
|
||||
+342
-92
@@ -1,20 +1,20 @@
|
||||
# 接口与页面映射总表
|
||||
|
||||
> 更新日期:2026-07-22
|
||||
> 阶段状态:阶段 0 已完成;导航栈与 T01 专项设计已完成三人终审,业务代码尚未实施
|
||||
> 接口状态:已完成 A04 注册响应和 T01 世系图专项核对;其余 OpenAPI 操作仍待逐项审查
|
||||
> 阶段状态:阶段 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` 用于人工阅读与跨工具导入。阶段 0 只保护了两份用户导出;当前仅对导航依赖、A04 注册响应和 T01 世系图做了专项核对,不能把专项结论扩张为 153 个操作均已审查。
|
||||
接口的唯一编辑源是后端维护的 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 保留源码但不属于活动路由。
|
||||
- 全项目响应式迁移与统一扫描已经完成,实际 `66/66` 个 Vue 文件均在覆盖清单中。
|
||||
- A 系列及 G01—G10 已由用户在 MuMu 中人工确认;G11、G12 以及 T、F、R、N、M 页面已由上一位代理按用户授权在 MuMu 中逐页、逐状态审核并修复。
|
||||
- 全项目响应式迁移与统一扫描已经完成;退役通用页面和零消费者旧表单删除后,实际 `64/64` 个 Vue 文件均在覆盖清单中。
|
||||
- 任务 5 实施前,A 系列及 G01—G10 曾由用户在 MuMu 中人工确认,G11、G12 以及 T、F、R、N、M 页面也曾逐页、逐状态审核并修复;这只是历史视觉基线,不覆盖任务 5—6 后的 G/T 动作、文案、权限和布局变更。任务 4—6 的当前代码仍须按实施计划重新完成 MuMu 流程矩阵。
|
||||
- 上述视觉结论是当前继续工作的基线,不等于真实接口、持久化、系统权限、真实短信、微信、支付或跨页数据闭环已经完成。
|
||||
- 后续发现明确、可复现的样式、交互或业务问题时可以重新打开页面;不得因为旧结论写着“通过”就忽略证据。
|
||||
|
||||
@@ -23,30 +23,30 @@
|
||||
### 2.1 账户、启动与登录
|
||||
|
||||
- APP 不设游客模式。首次打开、无有效凭证或凭证过期时进入 A01;有效登录态进入 G01。
|
||||
- A01 是唯一活动登录页,承载密码登录、短信登录、注册、找回密码、协议入口和微信登录入口。登录成功统一到 G01,不直接恢复上次浏览的深层页面。
|
||||
- A04 注册成功的目标是建立登录态后进入 G01。当前 OpenAPI 已确认 `POST /genealogy/app/auth/register` 的 `200` 响应复用 `LoginResult`,其 `LoginVo` 可返回 `token/accessToken/tokenValue`;正式接入应保存有效会话后直接进入 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` 位且必须同时包含字母和数字;M04 的新密码还不得与旧密码相同。页面不得各自复制或放宽该规则。
|
||||
- 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 最终共用真实行为验证能力,但触发时机必须按业务区分:A01 在本地校验通过后、登录请求前触发;A05 在请求发送短信验证码前触发,最终重设提交不重复验证;A04 当前临时在完整注册表单提交后触发,后端提供短信注册接口时必须迁移到发送短信前,并删除旧触发路径。关闭或验证失败时保留表单内容,不得形成两套并行验证路径。
|
||||
- 短信验证码需要完整状态机:可发送、发送中、倒计时、发送失败、限流、前后台恢复和到期。该状态机在导航栈统一之后单独评估,不在阶段 0 实施。
|
||||
- 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,不复制整份家谱数据。
|
||||
- G01 顶部当前家谱卡用于打开切换层;下方可用家谱卡直接进入 G05。只有导航成功后才同步顶部选中项和本机当前家谱 ID,失败或并发点击不得把页面与持久上下文串到不同家谱。首次且没有历史选择或失效标记时可确定使用首个可用家谱;调用方给定的新列表不再包含历史 ID,或显式目标不可用时,必须清空、持久标记并要求用户重选,跨重载也禁止静默回退到另一家谱。只保存词法字符串 ID,不复制整份家谱数据;真实撤权检测等待 workspace 接入 onShow/事件刷新。
|
||||
- 审核中记录只展示进度和“撤回申请”,顶部卡不可进入家谱;被拒绝记录展示原因和“修改后重新提交”,进入 G08 而不是 G05;已退出或被移除记录展示原因和“重新申请”,不得访问原家谱内容。
|
||||
- 只有可用家谱能够成为全局家谱上下文;审核中、被拒绝、已退出、被移除或待录入始祖的记录不得覆盖最近一个可用上下文。
|
||||
- 只有 READY 且当前账号 `canView=true` 的家谱能够成为全局家谱上下文;审核中、被拒绝、已退出或被移除的记录不得覆盖最近一个可用上下文。G03 新合同不再产生“待录入始祖”业务状态;历史半成品如真实存在应由后端迁移/隔离,不能继续成为客户端正式状态。
|
||||
- 没有可用家谱时,相关页面不得展示上一个失效家谱的缓存内容,应引导用户搜索家谱、使用邀请码或创建家谱。
|
||||
- G01 空态的主次顺序为搜索家谱、邀请码加入、创建家谱;非空列表保留“添加家谱”底部弹层。所有者可见世系、成员、字辈诗、申请审核四个快捷入口,普通成员不显示申请审核。
|
||||
- G06 同时承载搜索与邀请码定位。搜索申请进入审核;邀请码目标是直接加入,不应生成审核记录。邀请码验证和直接加入目前仍是待核对的接口依赖。
|
||||
- G06 同时承载搜索与邀请码定位。产品目标仍是“邀请码直接加入且不生成审核记录”,但当前 OpenAPI 没有邀请码校验或直接加入操作,因此本地流程只展示目标并进入 G08 填写确认,完成后无结果返回 G01,不能选中家谱或声称已经加入。搜索申请进入审核;真实邀请码终点必须等后端合同补齐后替换本地预览。
|
||||
- G06 结果至少需要谱名、姓氏、地区、堂号、所属上级谱、当前支系、管理者或认证信息、成员规模和最近更新时间,以区分同名家谱和支系;未加入、已加入、审核中、被拒绝、已退出或移除、我创建的六种关系各自只出现一个明确动作。
|
||||
- G08 当前用真实姓名、与已知长辈的文字关系和补充说明表达申请;用户可见示例统一使用“某某某堂侄”等通用占位,不出现具体姓名。后端提供结构化参照成员或关系字段后,应以新合同完整替换文字关系旧路径。
|
||||
- G03 当前只创建独立家谱。创建完成但始祖未录入时,G01 必须保留“待录入始祖”记录;完成始祖后进入 G05,由用户主动进入 T01,不自动越过家谱总览。
|
||||
- G03 当前在同一页面实例内依次完成“创建家谱”和“录入始祖”,不接收 `step` 或 `genealogyId` 路由参数;门禁前本地成功无业务结果进入 G05 明确预览态。生产目标不允许创建中断成空谱:第一步零网络写,最终按钮一次原子创建家谱、OWNER 与唯一始祖;进程终止后按 operationKey 查询服务端操作状态,不恢复已删除的路由步骤合同,也不让 G01 承担半成品恢复卡。
|
||||
- G05 同一路由区分公开预览与成员视图。公开预览不得闪现成员隐私或管理入口;所有者和普通成员采用最小权限模型,最终权限以接口合同为准。
|
||||
- G05 首屏按身份确认、来源确认、可信度确认三层组织信息;世系是次级入口,不自动抢占首次进入流程。
|
||||
- G11 只维护当前可解释的名称、公开范围和访问说明;公开范围只有“仅成员可见”和“公开可申请”两个当前枚举,不虚构“转让管理员”等后端尚未确认的能力。G12 字辈保存必须保证代次连续;发现历史缺口时停止向后推导并要求明确处理,不能静默错位。
|
||||
- G11 只维护当前可解释的名称、访问预设和家谱简介;门禁前本地 fixture 的“仅成员可见”“公开可申请”仍由 `utils/genealogy-contracts.js` 映射旧 `visibility/joinMode` 并对未知组合失败关闭。任务 35 已选定后端同版迁移为 APP 读、建、改唯一 `GenealogyAccessPreset`,落地时必须删除旧 pair 与客户端数字映射,不双读;不虚构“转让管理员”等能力。G12 本地批量预览固定按完整序列处理,支持最多 500 代、单代 50 字符和接口声明的分隔符;发现 ACTIVE 世代缺口时停止保存,不能静默错位。
|
||||
|
||||
### 2.3 页面状态与请求结果
|
||||
|
||||
@@ -61,13 +61,13 @@
|
||||
|
||||
### 2.4 导航、弹层与流程终点
|
||||
|
||||
- 当前三个业务根页面是 G01“家谱”、F01“家族”和 M01“我的”,A01 是认证根页;书面栈语义已经在导航设计中收口,业务代码仍须按测试先行和 MuMu 矩阵实施验证。
|
||||
- 当前三个业务根页面是 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 作为正式体验。
|
||||
- 轻提示、底部弹层、居中确认、结果说明和危险操作按决策成本分级。不可逆操作必须说明后果并二次确认;普通操作不滥用确认。
|
||||
- 产品合同已经定案为“邀请码直接加入且不生成审核记录”:只有邀请码校验成功才执行直接加入。M08 当前“邀请码加入后需要管理员审核”的旧文案属于待删除实现债务,导航任务 9 必须同步删除,不能保留审核与直接加入两套分支;邀请码校验和直接加入接口仍按对应业务阶段向后端核对。
|
||||
- 产品合同已经定案为“邀请码直接加入且不生成审核记录”:只有未来真实邀请码校验与直接加入接口成功才允许建立成员关系。当前 G06/G08 只做本地流程预览并明确未提交服务器;M08 的旧审核分支、硬编码邀请码、复制和海报伪能力已经删除,在真实邀请码签发与校验合同落地前保持不可用。
|
||||
|
||||
### 2.5 三个根页面与主要流程
|
||||
|
||||
@@ -93,20 +93,20 @@ M01 我的
|
||||
|
||||
### 2.6 当前本地数据与路由参数边界
|
||||
|
||||
当前 `52` 条活动页面仍使用页面内本地状态、fixture 或 mock 数据;活动页面没有 `appApi` 消费者。`utils/api.js` 的存在不能被解释为已经接入真实接口。以下只记录页面源码当前主动读取的查询参数,供阶段 1 导航栈与接口审查核对;上游传入但页面未读取的参数属于待审债务,不能写成有效合同。
|
||||
当前 `52` 条活动页面除 A01/A04/A05 的认证调用外,仍使用页面内本地状态、fixture 或 mock 数据;其他活动页面没有 `appApi` 消费者。认证调用已经对准真实端点,但 `runtimeConfig.mode` 固定为 `mock` 并失败关闭,不能解释为真实后端已经联通。以下只记录页面源码当前主动读取的查询参数;上游传入但页面未读取的参数属于待审债务,不能写成有效合同。
|
||||
|
||||
| 页面 | 当前主动读取的查询参数 |
|
||||
| --- | --- |
|
||||
| A01、A04、A05 | 无 |
|
||||
| G01 | `genealogyId`、`state` |
|
||||
| G03 | `step`、`genealogyId` |
|
||||
| G05 | `genealogyId`、`mode`、`role`、`state`、`genealogyName` |
|
||||
| G03 | 无 |
|
||||
| G05 | `genealogyId`、`state` |
|
||||
| G06 | `mode`、`state` |
|
||||
| G08 | `genealogyId`、`source`、`previous`、`state`、`genealogyName` |
|
||||
| G08 | `genealogyId`、`source`、`state` |
|
||||
| G09 | `state`、`status` |
|
||||
| G10 | `genealogyId`、`state` |
|
||||
| G11 | `genealogyId`、`state` |
|
||||
| G12 | `genealogyId`、`startGeneration`、`currentGeneration`、`state` |
|
||||
| G12 | `genealogyId`、`state` |
|
||||
| T01 | `genealogyId`、`state`、`selectedId` |
|
||||
| T03 | `genealogyId`、`personId`、`state` |
|
||||
| T04 | `genealogyId`、`personId`、`mode`、`state` |
|
||||
@@ -114,30 +114,32 @@ M01 我的
|
||||
| T07 | `genealogyId`、`state` |
|
||||
| T08 | `genealogyId`、`personId`、`state` |
|
||||
| F01 | `genealogyId`、`state` |
|
||||
| F02 | `state` |
|
||||
| F03 | `feedId`、`commentResult`、`state` |
|
||||
| F04 | `count`、`state` |
|
||||
| F05 | `articleId`、`state` |
|
||||
| F06 | `articleId`、`mode`、`state` |
|
||||
| F07 | `count`、`state` |
|
||||
| F08、F09 | `albumId`、`state` |
|
||||
| F10 | 无 |
|
||||
| R01 | `state` |
|
||||
| R02 | `mode`、`personId`、`state` |
|
||||
| R03 | `count`、`state` |
|
||||
| R04 | `giftId`、`mode`、`saveResult`、`state` |
|
||||
| R05 | `count`、`state` |
|
||||
| R06 | `ritualId`、`state` |
|
||||
| R07 | `ritualId`、`mode`、`saveResult` |
|
||||
| R08、R09 | `personName`、`state` |
|
||||
| R10、R11 | `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`、`count`、`saveResult` 等参数目前主要用于本地状态审查和压力测试,不代表后端请求字段。导航注册表已经决定删除 G03 的 `step`、G05/G08 的 `genealogyName` 和 G08 的 `previous`:页内步骤留在页面状态,名称按 `genealogyId` 从现有 fixture 或后续领域数据取得,来源只由真实栈与受验证 `sourceKey` 表达。其余参数仍须在对应业务阶段判断为真实输入、页内状态、领域数据或删除项,不得把调试参数固化成接口合同。
|
||||
`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 全局非功能门槛
|
||||
|
||||
@@ -147,66 +149,135 @@ M01 我的
|
||||
- 同一详情连续进入和退出 `20` 次,并快速切换根页面、重复开关弹层;不得出现白屏、串状态、重复堆栈、残留遮罩或逐次变慢。
|
||||
- Android 性能以约 `4GB` 内存的中低端设备为底线,验证启动、键盘、长列表、图片解码、页面切换和系统返回手势。
|
||||
- 页面离开时清理本页创建的定时器、监听器、上传任务和动画状态;连续使用不得积累重复请求或实例。
|
||||
- 当前尚未实施的五个独立阶段依次为:导航栈语义统一、T01 大规模世系树、短信验证码完整状态机、跨页面领域数据持久化、全局文字层级和无障碍第二轮。不得一次混合实施。
|
||||
- 当前导航阶段已完成任务 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 和 T01 已形成专项证据外,其余接口列继续标记“待对应业务阶段 OpenAPI 审查”,避免把旧思维导图、页面 mock 或 PC 接口误当成 App 合同。
|
||||
表中“返回或完成目标”描述业务意图,不表示现有导航 API 已经正确;导航栈阶段需要用源码扫描、测试和 MuMu 完整流程逐项验证。A04、G 系列第一轮、F 系列当前写边界、T01 和新线上差异已形成专项证据;其余接口列继续标记“待对应业务阶段 OpenAPI 审查”,避免把旧思维导图、页面 mock、旧离线快照或 PC 接口误当成当前 App 合同。
|
||||
|
||||
| 编号 | 页面 | 路由 | 当前业务目标 | 主要进入方式 | 返回或完成目标 | 必测状态 | 接口业务域 | 当前接口核对状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| A01 | 登录 | `pages/auth/a01-entry` | 完成密码、短信或微信认证并处理协议 | APP 启动、凭证失效、主动退出 | 成功进入 G01;取消或失败留在本页 | 密码、短信、协议错误、发送中、倒计时、授权取消、登录失败、凭证过期 | 认证与账户 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| A04 | 注册账号 | `pages/auth/a04-register` | 建立新账号并确认协议 | A01 注册入口 | 成功建立登录态并进入 G01;取消返回 A01 | 本地校验、行为验证、注册中、手机号占用、成功、失败、取消 | 认证与账户 | 已核对注册成功响应为 `LoginResult`;行为验证与短信流程仍待审 |
|
||||
| A05 | 重设密码 | `pages/auth/a05-reset-password` | 验证手机号并设置新密码 | A01 忘记密码 | 成功返回 A01 的密码登录态;取消返回 A01 | 验证码、行为验证、密码策略、不一致、提交中、成功、失败、取消 | 认证与账户 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G01 | 我的家谱 | `pages/genealogy/g01-my-genealogies` | 选择全局家谱并完成加入或创建分流 | 登录成功、根 Tab、业务完成回流 | 进入 G03、G05、G06、G09、G10、G12、T01 或 N01 | 正常、空、加载、失败、审核中、被拒绝、退出或移除、待录入始祖、切换弹层 | 家谱与成员关系 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G03 | 创建家谱 | `pages/genealogy/g03-create-genealogy` | 创建独立家谱并录入始祖 | G01 创建入口或待完善记录 | 创建后续接始祖步骤;始祖完成进入 G05;取消回来源 | 创建、重复提醒、创建失败、待完善、始祖校验、保存中、成功、中断恢复 | 家谱与成员关系 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G05 | 家谱总览 | `pages/genealogy/g05-genealogy-overview` | 浏览家谱身份、来源和可信度并提供管理入口 | G01、G06 公开预览、G09 通过结果、G03 完成 | 返回 G01;进入 T01、G08、G10、G11、G12 或 F01 | 公开预览、成员视图、所有者、加载、空、失败、无权限、新建引导 | 家谱与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G06 | 加入家谱 | `pages/genealogy/g06-search-genealogies` | 通过搜索或邀请码准确定位目标家谱或支系 | G01 空态或添加家谱弹层 | 进入 G05 公开预览、G08、G09;已加入或我创建时回 G01 并选中 | 初始、搜索中、结果、无结果、邀请码无效或过期、失败、六种用户关系 | 家谱搜索与邀请 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G08 | 关系确认与入谱 | `pages/genealogy/g08-join-application` | 填写真实姓名、关系和说明并提交申请或直接加入 | G06 选定目标、G05 公开预览、G09 重新提交 | 搜索来源成功进入 G09;邀请码成功回 G01 并选中新家谱 | 双来源、字段校验、提交中、成功、失败、重复提交、放弃填写 | 加入申请与邀请 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G09 | 我的申请 | `pages/genealogy/g09-my-applications` | 查看、撤回或修改加入申请 | G01 申请分组、G08 搜索申请成功、G06 审核中或被拒绝状态 | 已通过进入 G05;修改进入 G08;撤回后保留明确结果 | 列表、空、失败、待审、通过、拒绝、撤回、重新提交 | 加入申请 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G10 | 入谱审核 | `pages/genealogy/g10-application-review` | 所有者审核加入申请 | G01、G05 或 N01 审核消息 | 完成后刷新审核列表及来源计数;返回来源 | 列表、空、失败、通过确认、拒绝原因、提交中、无权限;拒绝字段使用 `aria-invalid`、`aria-describedby`、错误 `role="alert"` 并在空提交后聚焦 | 加入审核与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G11 | 家谱设置 | `pages/genealogy/g11-genealogy-settings` | 维护家谱名称、公开范围和访问说明 | G05 所有者管理入口 | 保存后刷新 G05;取消恢复原值并返回 | 加载、字段校验、保存中、成功、失败、无权限、未保存返回 | 家谱设置与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| G12 | 字辈诗 | `pages/genealogy/g12-generation-poems` | 浏览与维护字辈序列 | G01 快捷入口或 G05 | 保存后刷新列表;取消返回来源 | 列表、空、编辑、校验、保存中、失败、无权限、长列表 | 字辈与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| 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 编辑入口 | 保存后返回 T03 并刷新;取消回 T03 | 加载、字段校验、长简介、保存中、成功、失败、无权限、放弃确认 | 成员档案与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| T06 | 编辑关系 | `pages/tree/t06-edit-relationship` | 校正两个现有成员之间的关系 | T01 关系操作 | 保存后返回 T01 并刷新关系;取消回来源 | 成员选择、校验、冲突、循环关系、冲突规则弹窗、保存中、失败、无权限 | 亲属关系与权限 | 待对应业务阶段 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 | 进入 F02—F04、F07、F10、R01、R03、R05、R10、R11 | 加载、列表、空、失败、刷新、分页、无可用家谱 | 家族内容聚合 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F02 | 发布动态 | `pages/family/f02-publish-feed` | 发布家族文字或媒体动态 | F01 发布入口 | 成功回 F01 并刷新;取消保留或确认放弃 | 表单、空内容校验、长内容、媒体权限、提交中、成功、失败、重复提交、取消 | 动态发布与上传 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F03 | 动态详情 | `pages/family/f03-feed-detail` | 阅读动态并查看或提交评论 | F01 动态卡 | 返回 F01 并恢复现场 | 加载、正常、内容失效、失败、评论校验、提交失败、插入成功、无权限 | 动态与评论 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F04 | 谱文列表 | `pages/family/f04-article-list` | 分类、搜索和浏览谱文 | F01 谱文入口 | 进入 F05 或 F06;返回 F01 | 加载、列表、分类、搜索无结果并重置、空、失败、长列表、新建 | 谱文 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F05 | 谱文详情 | `pages/family/f05-article-detail` | 阅读、收藏和按权限编辑谱文 | F04 谱文卡 | 返回 F04;有权限进入 F06 | 加载、正常、收藏切换、编辑、失效、隐私、失败、无权限 | 谱文与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F06 | 编辑谱文 | `pages/family/f06-article-editor` | 新建或编辑谱文草稿 | F04 新建或 F05 编辑 | 保存或发布后回 F04/F05 并刷新;取消确认放弃 | 新建、编辑、校验、加载、草稿、保存中、失败保留并重试、成功回流、长正文 | 谱文编辑 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F07 | 相册列表 | `pages/family/f07-album-list` | 浏览和创建家族相册 | F01 相册入口 | 进入 F08;返回 F01 | 加载、列表、长列表、空、失败、相册导航、创建弹窗、校验、插入和轻提示、权限 | 相册 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F08 | 相册详情 | `pages/family/f08-album-detail` | 浏览照片墙和相册信息 | F07 相册卡 | 返回 F07;进入 F09 | 加载、照片墙、末张预览、Android 返回先关预览、空相册、相册失效、失败、权限 | 相册与媒体 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F09 | 上传照片 | `pages/family/f09-media-upload` | 选择照片、填写逐张说明并上传 | F08 添加照片入口 | 成功回 F08 并刷新;取消确认放弃 | 初始、权限、最多九张、增删、当前照片独立说明、必填、上传锁定与进度、取消、失败重试、成功 | 媒体上传 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| F10 | 家族视频 | `pages/family/f10-video-list` | 说明当前视频服务尚未开放 | F01 视频入口 | 当前只返回 F01 | 待开放、返回 F01;未来列表、上传和接口状态只登记为对应业务阶段依赖,不冒充当前功能 | 视频服务 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R01 | 人物录 | `pages/records/r01-people-list` | 搜索和浏览家族人物记录 | F01 人物录入口 | 进入 R02;返回 F01 | 加载、列表、搜索、无结果、空、失败、分页、新建权限 | 人物记录 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R02 | 人物详情 | `pages/records/r02-person-detail` | 查看、新建或编辑人物记录 | R01 人物卡或新建入口 | 保存后回 R01 并刷新;进入 R08/R09;取消回来源 | 查看、新建、编辑、校验、保存中、成功、失败、隐私、失效 | 人物记录与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R03 | 贺礼簿 | `pages/records/r03-gift-list` | 浏览、筛选和新增贺礼记录 | F01 贺礼簿入口 | 进入 R04;返回 F01 | 加载、列表、空、失败、筛选、分页、新增权限 | 贺礼记录 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R04 | 贺礼编辑 | `pages/records/r04-gift-editor` | 查看、新增、编辑或删除贺礼 | R03 记录或新增入口 | 保存或删除后回 R03 并刷新;取消回来源 | 查看、新增、编辑、校验、保存中、成功、失败、删除确认、无权限 | 贺礼记录与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R05 | 礼仪列表 | `pages/records/r05-ritual-list` | 浏览和创建家族礼仪活动 | F01 礼仪入口 | 进入 R06 或 R07;返回 F01 | 加载、列表、空、失败、活动状态、分页、新建权限 | 礼仪活动 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R06 | 礼仪详情 | `pages/records/r06-ritual-detail` | 查看礼仪信息、参与者和状态 | R05 活动卡 | 返回 R05;有权限进入 R07 | 加载、详情、参与者、失败、失效、无权限 | 礼仪活动与参与 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R07 | 礼仪编辑 | `pages/records/r07-ritual-editor` | 新建或编辑礼仪活动 | R05 新建或 R06 编辑 | 保存或删除后回 R05 并刷新;取消回来源 | 新建、编辑、校验、保存中、成功、失败、删除确认、无权限 | 礼仪活动与权限 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R08 | 成长日志 | `pages/records/r08-growth-journal` | 展示人物成长时间轴并新增记录 | R02 或 T03 人物入口 | 保存后插入时间轴并给出轻提示;返回人物来源 | 加载、时间轴、空、失败、新增弹窗、必填、长文内部滚动、保存、权限 | 人物成长记录 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R09 | 人生事 | `pages/records/r09-life-events` | 展示人物人生事件时间轴并新增记录 | R02 或 T03 人物入口 | 保存后插入时间轴并给出轻提示;返回人物来源 | 加载、时间轴、空、失败、新增、校验、保存、权限 | 人生事件 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R10 | 家族备忘 | `pages/records/r10-memo-list` | 管理家族备忘和完成状态 | F01 备忘入口 | 新增或切换完成后刷新本页;返回 F01 | 加载、列表、空、失败、新增校验、完成、重新打开、重复操作、权限 | 家族备忘 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| R11 | 功德记录 | `pages/records/r11-merit-records` | 记录贡献并展示汇总 | F01 功德录入口 | 新增后实时刷新汇总和列表并给出轻提示;返回 F01 | 加载、汇总、列表、空、失败、新增、校验、保存中、权限 | 功德与贡献 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| N01 | 消息中心 | `pages/notification/n01-message-center` | 汇总消息、维护已读状态并分流业务 | G01 或 M01 消息入口 | 进入 N02 或对应 G10 等业务页面;返回来源 | 加载、未读、已读、全部已读、空、失败、审核消息、分页 | 消息与通知 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| N02 | 消息详情 | `pages/notification/n02-message-detail` | 展示消息正文并安全跳转到业务目标 | N01 消息卡 | 返回 N01;有效目标进入对应业务页 | 加载、详情、已读、失效消息、无目标、业务目标过期、失败 | 消息与业务分流 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M01 | 我的 | `pages/profile/m01-profile-home` | 展示个人资料、提醒和服务导航 | 根 Tab | 进入 M02、M03、M06、M08、M09、M10 或 N01 | 加载、正常、失败、提醒、资料不完整、服务可用性 | 个人中心聚合 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M02 | 个人资料 | `pages/profile/m02-edit-profile` | 查看并编辑头像和基础资料 | M01 资料入口 | 保存后回 M01 并刷新;取消回来源 | 加载、头像权限、字段校验、保存中、成功、失败、未保存返回 | 用户资料与上传 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M03 | 账号与安全 | `pages/profile/m03-security-settings` | 汇总密码、手机号和设备安全入口 | M01 安全入口 | 进入 M04 或 M05;返回 M01 | 加载、正常、异常提醒、失败、设备状态 | 账号安全 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M04 | 修改密码 | `pages/profile/m04-change-password` | 验证旧密码并设置新密码 | M03 密码入口 | 成功回 M03 或按安全合同重新登录;取消回 M03 | 旧密码错误、统一密码策略、新旧相同、不一致、提交中、成功、失败、重复提交 | 账号安全 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M05 | 修改手机号 | `pages/profile/m05-change-phone` | 验证并更换绑定手机号 | M03 手机号入口 | 成功回 M03 并刷新;取消回 M03 | 当前身份校验、新号码、验证码、倒计时、号码占用、成功、失败 | 账号安全与短信 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M06 | 帮助中心 | `pages/profile/m06-help-center` | 搜索和浏览帮助内容 | M01 帮助入口 | 返回 M01;无法解决时进入 M07 | 加载、分类、搜索、无结果、失败、内容失效 | 帮助内容 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M07 | 意见反馈 | `pages/profile/m07-feedback` | 提交问题说明和联系信息 | M06 联系入口 | 成功给出明确结果后返回 M06或 M01;取消回来源 | 校验、附件权限、提交中、成功、失败、重复提交、取消 | 用户反馈与上传 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M08 | 应用推广 | `pages/profile/m08-promotion` | 生成并分享家谱邀请信息 | M01 推广入口 | 分享成功、取消或失败均留有明确结果;返回 M01 | 邀请码、海报生成、系统分享权限、取消、失败、过期 | 邀请与系统分享 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M09 | VIP 与订单 | `pages/profile/m09-vip-orders` | 展示服务权益和订单;当前明确未开放付费 | M01 服务入口 | 当前关闭说明并返回 M01;未来进入合规订单流程 | 待开放、无订单、订单列表、加载失败、支付取消、退款边界 | 服务权益、订单与支付 | 待对应业务阶段 OpenAPI 审查 |
|
||||
| M10 | 关于家谱 | `pages/profile/m10-about-settings` | 展示版本、协议、隐私并处理退出登录 | M01 设置入口 | 协议关闭留在本页;退出成功清凭证并回 A01;取消留在本页 | 版本、协议、隐私、退出确认、取消、退出失败 | 配置、协议与认证 | 待对应业务阶段 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 |
|
||||
|
||||
## 四、封存与已移除页面
|
||||
|
||||
@@ -236,7 +307,8 @@ M01 我的
|
||||
|
||||
- 页面与动作:A04 提交注册并建立会话。
|
||||
- 当前接口:`POST /genealogy/app/auth/register`。
|
||||
- 已确认响应:HTTP `200` 复用 `LoginResult`,`data` 引用 `LoginVo`,可返回 `token/accessToken/tokenValue`。
|
||||
- 已确认响应:受保护旧快照的响应形状已经过期;2026-07-22 新线上注册成功响应为 `RAppLoginVo`,`data` 引用 `AppLoginVo`,唯一会话字段为 `access_token`。密码登录与短信登录使用同一响应链。
|
||||
- 客户端合同:`utils/api.js` 只消费 `AppLoginVo.access_token`;旧字段读取已经删除。必须等同版本双导出落地后再把页面接到远端,不能把线上证据手工写回受保护源文件。
|
||||
- 产品结论:取得并保存有效令牌后直接清理认证流程并进入 G01;不保留“注册成功后再登录”的并行终点。
|
||||
- 尚未关闭范围:短信发送、行为验证、限流和验证码状态机不由本结论代替,按后续短信阶段单独审查。
|
||||
|
||||
@@ -274,9 +346,187 @@ PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relatio
|
||||
- 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 动态重绘光晕。
|
||||
- OpenAPI 必须列出 `400/401/403/404/422/429/5xx`、409、字符串 ID、nullable 头像和 `additionalProperties: false`。
|
||||
- 四条操作统一声明 `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 返回矩阵全部通过。
|
||||
|
||||
当前其余已知但尚未核实的重点依赖包括:微信登录、公共行为验证、完整短信状态机、邀请码验证与直接加入、结构化亲属关系、上级家谱与支系权限、管理员授权与功能开关、上传与系统权限、消息业务目标、系统分享、订单支付与退款。它们只表示审查重点,不预判后端一定缺失。
|
||||
|
||||
+4
-1
@@ -13,6 +13,8 @@
|
||||
|
||||
schema v3 注册入口是 `design-pipeline/manifests/runtime-assets.json`。任何新增的 `runtime-asset-inventory` 或 `asset-build-manifest` 都必须进入该注册表的导入闭包;未注册 owner、重复资产 `id` 与重复正式输出都会被拒绝。当前 `static/assets` 中的每个文件都必须恰好属于一个正式 owner,并至少存在一个真实运行时消费者。
|
||||
|
||||
导航任务 3 删除了无活动消费者的通用页面旧入口,并同步删除两张仅靠该入口人工补数的 notification frame。当前注册表闭合 `77` 个正式 `static/assets` 输出;family/profile/records 的同名 frame 仍有真实专项 mixin 消费,`ModulePageBackground` 仍有活动页面消费者,均继续保留。`tests/retired-module-page-contract.ps1` 是这次退役边界的防回归所有者。
|
||||
|
||||
认证直接资产由 `design-pipeline/manifests/auth-runtime-assets.json` 管理,其余无法重建但仍被产品消费的直接二进制由 `design-pipeline/manifests/application-runtime-assets.json` 管理。四张共享卷轴、六张长页面背景和 G01 空态边框的生成事实依次只属于 `design-pipeline/manifests/shared-scroll-skins-v3.json`、`design-pipeline/manifests/page-backgrounds-v3.json` 与 `design-pipeline/manifests/g01-state-frame-v3.json`。
|
||||
|
||||
页面、组件、样式、数据映射和工具代码本身是消费者关系的唯一事实源。生成清单不得保存槽位、Vue 组件、选择器、`uni-app mode`、消费者列表或其他运行时渲染语义;这些规则只能由实际消费者源码及对应合同拥有。
|
||||
@@ -62,6 +64,7 @@ npm.cmd run verify:shared-scroll-skins
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File tests/runtime-assets-contract.ps1
|
||||
powershell -ExecutionPolicy Bypass -File tests/retired-asset-removal-contract.ps1
|
||||
powershell -ExecutionPolicy Bypass -File tests/retired-module-page-contract.ps1
|
||||
powershell -ExecutionPolicy Bypass -File tests/a01-retired-pipeline-removal-contract.ps1
|
||||
powershell -ExecutionPolicy Bypass -File tests/a01-no-photoshop-pipeline-contract.ps1
|
||||
powershell -ExecutionPolicy Bypass -File tests/mumu-visual-acceptance-boundary-contract.ps1
|
||||
@@ -94,4 +97,4 @@ powershell -ExecutionPolicy Bypass -File tests/compile-audit.ps1
|
||||
- `docs/design/assets/a01-vnext/A01-shared-scroll-skins-with-dialog-approved.png`
|
||||
- `docs/design/assets/a01-vnext/A01-shared-skin-family-option-2-selected.png`
|
||||
|
||||
G01 的 A/B/C 方向候选、无确定变换的 add-sheet/close 色键源、重复的旗舰 ImageGen 源、旧 `static/icons` 和未接入业务的 TAC 文件已经退役。正式 add-sheet 与 close 位图作为 `committed-binary` 由 `application-runtime-assets.json` 锁定;六张长背景及 G01 空态边框则保留可执行母版和 schema v3 构建链。不得重新引入候选入口、虚构可重建关系或在文档复制正式输出哈希。
|
||||
G01 的 A/B/C 方向候选、无确定变换的 add-sheet/close 色键源、重复的旗舰 ImageGen 源、旧 `static/icons` 和阶段 0 当时存在的未接入 TAC 旧批次已经退役。`static/tac/` 当前共 5 个文件:4 个后端提供的供应商文件保持原字节,项目只新增 `static/tac/js/jiapu-tac-adapter.js` 作为唯一协议适配层。A01/A04/A05 已通过 `components/TacVerification.vue` 形成真实运行时消费者;这组供应商资产不是视觉构建管线的可再生输出,不进入 `runtime-assets.json`,其存在性、精确文件集合、消费者和哈希由 `tests/auth-tac-integration-contract.ps1` 唯一拥有。受保护哈希分别为 `tac.css=181694518971a9f991d551b6a6e6dab2bf750f940bfc1673a158213f92eedbe0`、`tac.min.js=505f73c051908d7b805db458990790be3e91f792c4001cec0ea9377d7d302b55`、`icon.png=53e37ffc5bb81c46e6306b7d61d2eaa3de57e47ca6cdb8d5210022ae815c21c2`、`dun.jpeg=d9178a8c4cca36e3df6c3acd7e895ce9d34dd60ef3f1cf4a70c94d4324ed96e7`;不得修改、格式化、覆盖或以重新下载文件替换。正式 add-sheet 与 close 位图作为 `committed-binary` 由 `application-runtime-assets.json` 锁定;六张长背景及 G01 空态边框则保留可执行母版和 schema v3 构建链。不得重新引入候选入口、虚构可重建关系或复制第二套 TAC 资产所有权。
|
||||
|
||||
+173
-32
@@ -1,13 +1,52 @@
|
||||
# 项目当前总览
|
||||
|
||||
> 当前阶段:阶段 1——导航栈与 T01 大规模世系树书面设计已收口
|
||||
> 当前状态:三位审查者已完成导航与 T01 的独立审查、交叉补漏和终审;业务代码尚未实施,下一步从导航失败测试开始
|
||||
> 当前提交:`eced3d1 完成全项目响应式审核与换机交接`
|
||||
> 更新日期:2026-07-22
|
||||
> 当前阶段:导航任务 1—10、TAC 认证客户端、领域上下文基础与 M07 反馈客户端已经完成;T01、认证、家谱工作区、G03 原子创建、M06 帮助、个人资料读写、通知读写、M10 服务端退出、M04 登录态改密和 M05 手机号换绑后端接口门禁均为红灯,继续逐域关闭真实接口
|
||||
> 当前状态:A01/A04/A05 已接入统一 TAC、真实短信和认证请求代码,M07 已接真实反馈提交 owner;G01/G05、G03、M06、M01/M02/M03 个人资料读写、N01/N02/M01/G01 通知域、M10 退出域、M04 密码凭证域与 M05 换绑域已完成三人接口审查和失败门禁,均未猜测接线;`runtimeConfig.mode` 仍为 `mock`,MuMu 原生矩阵和真实环境联调待执行
|
||||
> 当前基准 HEAD:`9b0ad62df467f4e2c58b7689087683c77755e07b`(`main`,工作区有本轮未提交变更)
|
||||
> 更新日期:2026-07-23
|
||||
|
||||
## 2026-07-23 换机续作断点
|
||||
|
||||
### 总目标与完成定义
|
||||
|
||||
本项目的总目标不可缩减为“只写规划”或“只让静态测试通过”:必须在 `main` 工作区内持续推进整个 UniApp 家谱项目,按测试先行和三人交叉评审完成导航、T01 长世代世系树、TAC/认证、家谱领域数据、G/T/F/R/N/M 全量页面与接口、异常恢复、无障碍、构建及上线验证,最终交付能够正常上线使用的项目。若本地可完成的工作全部完成后只剩后端合同重导、有效测试账号、应用签名、发布凭证或 MuMu 人工操作等外部状态,才允许停在明确硬阻塞;每项阻塞必须有失败门禁、复现证据、唯一所有者、解除条件和继续步骤,不能用 mock、fixture、定时器或宽松兼容伪装完成。
|
||||
|
||||
协作固定为当前主代理加两位评审者,共三人。三人都必须独立检查接口/字段、页面/业务闭环、交互/异常/视觉,再交叉质询并统一结论;主代理是唯一写入者,不再创建旧专家身份或增加并发评审者。普通技术取舍由三人自行收敛,不反复交给用户。系统保存的目标记录仍存在,但在 2026-07-23 读取时状态为 `paused`;换机后的执行者不得据此把任务视为取消,应以上述总目标和本节断点继续。只有项目真实达到完成定义时才能标记完成。
|
||||
|
||||
### 仓库与保护基线
|
||||
|
||||
- 当前分支为 `main`,HEAD 为 `9b0ad62df467f4e2c58b7689087683c77755e07b`,上游显示 `main...origin/main`。工作区含大量本轮未提交的修改、删除和新增文件,均属于当前连续治理成果;换机后先运行 `git status -sb` 和 `git rev-parse HEAD` 核对,禁止用 `restore/checkout/reset` 清理,也禁止自行改分支或 worktree。
|
||||
- 本节仅记录断点,没有执行 `git add/commit/push`。由用户自行上传;换机后必须保留当前完整工作区,而不能只依赖旧远端基线。
|
||||
- 后端唯一目标地址是 `https://backend-api.ddxcjp.cn/`。受保护 `APP.openapi.yaml` 的 SHA-256 为 `8964CD583CE172425B63BBFD802F7EB587EB3641EADFD6F9D3B264FAA8090C6C`,`APP.openapi.json` 为 `87DB1DC148C5E6E877815AFF7B3A7FC7C7ECA95A2CEC50A88B42F3908961F31A`;两文件相对 HEAD 无差异。不得修改、格式化、覆盖或删除,只接受后端同一版本重新导出的双文件。
|
||||
- 不得启动、关闭或调整 MuMu。浏览器截图和源码检查不能冒充 Android 原生视觉、TalkBack、系统字号、软键盘或返回键验收。
|
||||
- 当前物理测试库存仍是 PowerShell `140`、Node `47`。最近一次完整记录为 PowerShell `126/140`,14 项均是预期发布红灯;Node 语法 `47/47`、纯 Node `19/19`、活动 Vue 脚本 `64/64` 通过。G03 最后一次布尔类型加固后又单独通过聚焦合同;换机后应先重跑完整套件,不能把这些历史数字冒充新机器证据。
|
||||
|
||||
### 已完成到哪里
|
||||
|
||||
导航任务 1—10、统一 TAC 客户端、领域上下文基础和 M07 真实反馈客户端已经完成本地可完成部分。T01、认证/TAC、家谱工作区、M06、个人资料读写、通知读写、退出、密码、手机号换绑均已经三人审查并建立明确 OpenAPI 红灯。任务 35 已完成 G03 原子创建合同:否决空谱加通用人物的两写方案,固定一次 atomic bootstrap、无 PII operation-status、统一 accessPreset、可信地区、词法 ID、始祖不变量及客户端激活门禁;`openapi-yaml-json-parity-runtime-smoke.js` 已升级为严格 YAML mapping 和任意精度数字的完整双源深比较。尚未批量把这些红灯域接到宽松线上接口,`runtimeConfig.mode` 仍诚实保持 `mock`。
|
||||
|
||||
### 当前精确断点:任务 36 普通加入申请闭环
|
||||
|
||||
三人已经完成 G06/G08/G09/G10 与 2026-07-22 线上 OpenAPI 的只读核对,范围只包括“鉴权搜索公开可申请家谱 → 普通申请 → 我的申请/撤回 → 待审列表/单条审核”。邀请码校验和直接加入仍是后续独立任务,产品结论保持“邀请码成功后直接加入且不生成审核记录”,不得混入普通审核合同。
|
||||
|
||||
已统一的最小正确方案如下:
|
||||
|
||||
- 搜索、我的申请和待审列表使用各自专用最小投影与稳定 cursor 分页,不返回 `total`,所有 `genealogyId/applyId` 都是有界词法字符串。搜索项只暴露识别家谱所需字段和当前查看者状态;我的申请完整表达 `PENDING/APPROVED/REJECTED/WITHDRAWN`;待审项只含 `applyId/applicantName/relationDesc/applyReason?/submittedAt`,不得泄漏手机号、用户 ID、邀请人或审核人内部字段。
|
||||
- 申请 body 闭合为 `applicantName/relationDesc/applyReason?`,前两项必填;删除 `phone/inviterUserId`。同账号、租户、家谱最多一个活动 `PENDING`,由数据库唯一约束而非先查后插保证。
|
||||
- 申请 POST 必须带无 PII 的 `Idempotency-Key`。同 key、同 canonical body 重放同一结果;同 key、不同 body 返回 409。客户端只持久 `{sessionEpoch,requestKey,startedAt}`,不持久姓名、关系和理由。
|
||||
- 冷启动恢复不复用 `mine` 查询,也不持久表单 PII;新增唯一只读 owner `GET /genealogy/app/genealogies/join-apply-requests/{requestKey}`,以显式 discriminator 返回 `PENDING {resolveBy,retryAfterSeconds}`、`SUCCEEDED {applyId/genealogyId/...无 PII 回执}` 或 `FAILED_NO_COMMIT`。只允许 `ABSENT→PENDING→SUCCEEDED/FAILED_NO_COMMIT`,两个终态不可变;GET 纯读,跨账号/租户/client 统一非泄漏 404,PENDING 必须有收敛期限,从未到达的 key 也必须在可计算时间边界后成为零写 `FAILED_NO_COMMIT`。
|
||||
- 不引入 `applicationVersion`、`If-Match` 或审核详情端点。申请在 PENDING 时不可编辑,撤回、通过和拒绝都以 `WHERE status=PENDING` 的数据库 CAS 决定唯一赢家。重复相同撤回返回同一 200;重复相同审核决定返回原 200,拒绝时只有规范化后理由相同才算相同动作;相反决定、不同拒绝理由或撤回/审核竞态败方返回 typed 409 和当前最小状态。终态不可改,重新申请创建新 `applyId`。
|
||||
- 审核 body 只允许 `APPROVE`,或 `REJECT+必填申请人可见 rejectionReason`。批准必须在同一事务完成唯一成员关系和申请终态;G05 的 `canReviewJoinApplications` 只是入口 capability,服务端在事务/CAS 时仍重新验证权限、家谱状态和 `PUBLIC_APPLY`。
|
||||
- 所有操作 required SaToken 和非空 `clientid`,仅使用 `application/json`、`Cache-Control: private, no-store`,429 带 `Retry-After`。线上三个匿名 GET 当前实测为 HTTP 200 加业务 `code=401`,却与文档 HTTP 401 string 冲突;发布合同必须统一为真实 HTTP 状态与 typed JSON,禁止 `*/*`、200 包装认证错误、`default` 响应和 int64 JSON 身份。
|
||||
- 客户端结果未知时禁止乐观改列表:申请查专用 operation;撤回刷新 mine 并可安全重放同一 DELETE;审核刷新 pending,行消失只能说“状态已变化/已被处理”,不能冒充本次审核成功。首版不承诺消息中心通知;G09 的 `onShow` 与手动刷新是当前业务真相。
|
||||
|
||||
**尚未实施的边界必须原样保留:** `tests/join-application-openapi-contract.ps1` 还没有创建,任务 36 尚未写入实施计划和接口映射,测试计数仍为 140/47;G06/G08/G09/G10 业务代码也没有在本任务中修改。换机后的第一项写操作应是用 `apply_patch` 新增该 OpenAPI 失败门禁,先运行并取得精确 `JOIN-APPLICATION-OPENAPI-CONTRACT BLOCKED`,再把 `API-JOIN-001`—`006`、七个 operation、状态机、隐私投影、竞态和部署反例同步写入现有四份中文权威文档及文档合同。后端门禁通过前页面继续保持诚实本地预览,不写只适配当前宽松线上模型的临时代码。
|
||||
|
||||
完成任务 36 的本地门禁、全量回归和三人终审后,继续按独立批次推进邀请码直入、G11 设置写入、G12 字辈真实保存,以及其余 G/T/F/R/N/M 业务域;不得一次混合导航、验证码、领域持久化和无障碍多个阶段。最终仍须完成真实后端联调、构建、签名/隐私配置、MuMu 全流程矩阵和发布终审。
|
||||
|
||||
## 当前目标
|
||||
|
||||
阶段 0 的清理和验证基线已经完成。当前主代理与两位固定评审者已把下一轮工作收敛为两个严格串行阶段:先统一导航栈语义,再在后端新图合同通过后重建 T01 大规模世系树。当前只完成中文设计和实施计划,没有接入新接口、迁移导航业务代码或改动 T01 运行时。
|
||||
阶段 0 的清理和验证基线已经完成。当前主代理与两位固定评审者已完成导航任务 1—10,建立路由注册表、导航网关和零债务门禁,并迁移共享组件与认证、G、T、F、R、N/M 全部活动页面。T01、认证/TAC、G01/G05 家谱工作区、G03 原子创建、M06 帮助、M01/M02/M03 个人资料读取、M02 个人资料写入、N01/N02/M01/G01 通知读写、M10 当前设备退出、M04/全认证密码凭证及 M05 手机号换绑的规范 OpenAPI 合同都已测试先行落地;TAC 客户端批次已经完成,A01 短信登录、A04 注册和 A05 忘记密码当前共用严格验证组件、服务端 `validToken`、4 位短信码与可取消请求,但生产 OTP 目标已收紧为统一 6 位,须等待后端同版原子迁移后再替换客户端,禁止 4/6 双接受。领域基础已把会话与当前家谱 ID 收紧为词法字符串:账号切换和损坏存储清理上下文;给定新列表发现历史 ID 消失或显式目标无权时写入持久失效标记,跨重载也禁止静默切谱;T01 也会在写入路由家谱前拒绝已有失效标记和无访问权限的夹具。M07 已按 `POST /genealogy/app/feedback` 接入严格真实提交,mock 模式固定返回 `WRITE_UNAVAILABLE`,不再伪造成功。后端认证门禁未关闭前保持 `mock`;其余尚未接真实写接口的页面仍是明确本地预览或硬关闭。
|
||||
|
||||
## 当前权威资料
|
||||
|
||||
@@ -18,7 +57,7 @@
|
||||
- 接口导出:`APP.openapi.json`、`APP.openapi.yaml`
|
||||
- schema v3 视觉资产注册表:`design-pipeline/manifests/runtime-assets.json`
|
||||
- 九宫格所有者:`styles/adaptive-frame-profiles.scss`
|
||||
- 密码策略所有者:`utils/validation.js`
|
||||
- 当前预览密码策略所有者:`utils/validation.js`;生产目标 wire/policy owner 等待 `API-PASSWORD-001`—`005` 关闭后原子替换,禁止单页双轨。
|
||||
- 响应式覆盖:`tests/responsive-layout-coverage.json`
|
||||
- 固定尺寸例外:`tests/responsive-layout-allowlist.json`
|
||||
- 活动路由:`pages.json`;项目固定使用 Vue 3,`uni.scss` 是 Sass 设计令牌唯一入口。
|
||||
@@ -27,18 +66,41 @@
|
||||
|
||||
## 接口文档所有权
|
||||
|
||||
Apifox 是接口合同的唯一源头。`APP.openapi.json` 用于自动扫描和测试,`APP.openapi.yaml` 用于人工阅读与跨工具导入。两份文件都由用户从 Apifox 导出;后续目标合同要求它们来自同一版本并保持语义一致,不得分别手工维护。阶段 0 只保护原文件,尚未把“当前两份导出语义完全一致”当作已验证结论。
|
||||
后端维护的 Apifox 项目是接口合同的唯一编辑源。`APP.openapi.json` 用于离线自动扫描和测试,`APP.openapi.yaml` 用于人工阅读与跨工具导入;两份文件都只接受来自同一后端版本的重新导出,不得分别手工维护。部署地址的 `/v3/api-docs` 只作为当前线上实现证据,发现差异时必须推动同版本双导出更新,不能反向覆盖受保护文件。
|
||||
|
||||
当前 JSON 文档为 OpenAPI `3.0.1`,包含 `112` 条路径、`153` 个操作和 `72` 个模型。完整的 JSON/YAML 语义一致性合同将在阶段 1 建立。
|
||||
当前 JSON 文档为 OpenAPI `3.0.1`,包含 `112` 条路径、`153` 个操作和 `72` 个模型。`tests/openapi-yaml-json-parity-runtime-smoke.js` 已使用无第三方依赖、无损任意精度数字且严格校验 mapping 分隔符的结构化解析器深比较完整 JSON/YAML;相邻不安全大整数差一反例已先红后绿,当前两份受保护快照语义一致。后端今后必须同版本双导出,任何单边漂移都会在领域合同前失败。
|
||||
|
||||
阶段 0 结束时,JSON 的 SHA-256 为 `2b5b9a0ffdcd901c361fb7500bfd354d5fd6d038639fb6350c7d1ef7c0d43ab9`,YAML 的 SHA-256 为 `ce6553577d441fee8c5023a87c30f9b411469cc1e7ce2d3c237314df0272ad77`。Git 只读状态仍为 YAML 已修改、JSON 未跟踪,与接管时一致;阶段 0 没有改写两份导出。
|
||||
后端在 2026-07-22 新提供 `https://backend-api.ddxcjp.cn/`。同日 21:52(Asia/Shanghai)只读获取其 `/v3/api-docs`,线上为 OpenAPI `3.1.0`、`722` 条路径、`858` 个操作和 `507` 个模型;本地 112 条路径中有 109 条仍在线,`/genealogy/app/files/reference`、`/genealogy/app/files/upload`、`/genealogy/pc/files/upload` 三条不在当前线上文档,线上另有 613 条路径。线上模型已把部分 `Genealogy*Body` 重命名为 `AppGenealogy*Body`,`GenerationPoemBatchBody` 也新增了由路径写入的 `genealogyId` 字段,因此本地双导出是明确的旧快照,不能再代表当前部署的完整合同。`utils/config.js` 的唯一 `baseUrl` 已更新为无尾斜杠的 HTTPS 地址;`mode` 仍保持 `mock`,在页面接口和安全合同逐批闭合前不得提前切换远端。
|
||||
|
||||
## 必须保护的用户改动
|
||||
线上文档已经提供 `/captcha/require`、`/captcha/challenge`、`/captcha/verify`:挑战与校验绑定 `tenantId/clientId/sceneCode/subject`,校验成功响应可返回 `validToken`,而发送短信的 `AppSmsCodeBody` 已把 `validToken` 列为必填。客户端已按 `APP_SMS_LOGIN/APP_REGISTER/APP_FORGOT_PASSWORD` 三个精确场景实现“查询要求→取得挑战→供应商完成→服务端校验→携票发送短信”,注册和找回提交不重复执行 TAC;认证请求只接受 HTTP 200 的严格 JSON envelope,统一 15 秒超时,离页或返回会中止当前 RequestTask。密码登录体尚无 `validToken`,因此 A01 以短信登录为默认且密码登录入口保持不可用,绝不以客户端先滑动冒充服务端强制校验。
|
||||
|
||||
- `APP.openapi.yaml`:用户当前修改。
|
||||
- `APP.openapi.json`:用户新导出的未跟踪文件。
|
||||
- `docs/家谱项目全量治理设计.md`:已确认中文设计。
|
||||
- `docs/家谱项目全量治理实施计划.md`:已改写为导航栈与 T01 的当前中文实施计划。
|
||||
认证后端门禁仍有四组问题:`API-AUTH-TAC-001` 要求密码登录加入服务端可消费的同语义票据;`API-AUTH-TAC-002` 记录线上 `/captcha/challenge` 在 `APP_REGISTER` 实测返回 HTTP 500 且空响应;`API-AUTH-TAC-003` 要求验证请求以 provider discriminator/`oneOf` 严格关闭根对象和各 payload 的额外字段,并补齐必填 `providerCode/captchaType/payload`;`API-AUTH-TAC-004` 要求同一验证中心返回服务端绑定的 verification session、可验证方法和同一类短时单次 `validToken`,`required=false` 也必须直接签发可供短信接口消费的票据,不得形成无障碍绕过。`tests/auth-tac-openapi-contract.ps1` 当前输出 `AUTH-TAC-OPENAPI-CONTRACT BLOCKED`。密码登录、短信登录和注册的线上成功响应已统一为 `RAppLoginVo → AppLoginVo.access_token`,客户端旧令牌字段读取已删除。线上文档自身仍发布 `http://backend-api.ddxcjp.cn` server URL,与已验证可用的 HTTPS 地址不一致;客户端只能使用显式 HTTPS,后端还需修正文档 server 声明。对 `http://localhost:5173` 的预检已返回允许 `content-type/clientid`,这不能替代正式 H5 域名的 CORS 验证。
|
||||
|
||||
家谱工作区只读审查已固定 `/genealogy/app/genealogies/mine` 为可访问集合 owner、`/genealogy/app/genealogies/{genealogyId}/overview` 为 G05 唯一详情 owner;首批不同时请求语义重复的 `/{genealogyId}`。线上 `AppGenealogyVo.genealogyId` 仍是 JSON `integer/int64`,最大合法值进入 JavaScript 后会不可逆失真;三个相关模型均无 `required`,也没有必填 `canView`,`roleType` 无枚举。无令牌实测三条读取均返回 HTTP 200、`application/json;charset=UTF-8` 与 `{code:401,msg,data:null}`,但文档只列 200/401 且 401 为字符串,尚不能稳定区分登录失效、对象无权、已删除和服务故障。问题单 `API-GENEALOGY-WORKSPACE-001`—`003` 与 `tests/genealogy-workspace-openapi-contract.ps1` 已建立;同版本双导出通过前不写 G01/G05 专属 adapter,不用本地字段猜测替代服务端 owner。
|
||||
|
||||
M06 三人审查选择 `GET /genealogy/app/help-articles` 的完整列表作为唯一远端 owner;线上列表模型已经包含 `helpContent`,因此页面不调用详情端点、不消费 `helpId`,也不让 JSON `int64` 进入页面模型。每次响应只允许显式投影 `helpCategory/helpTitle/helpContent`,分类从当前列表动态派生,展开键只在当前响应生命周期内使用并在搜索、分类、刷新前清空。当前受保护双导出仍返回通用 `ListResult/RList`,线上 `RListHelpArticleVo/HelpArticleVo` 又没有 `required`,正文格式、仅发布内容、展示顺序和认证失败承载也未形成一致合同;匿名实测列表与详情均为 HTTP 200+业务 `code=401`,而线上文档声明 HTTP 401 string。问题单 `API-M06-001`—`003` 与 `tests/help-center-openapi-contract.ps1` 已建立;门禁通过前保留明确本地 FAQ,不写 live-only adapter。
|
||||
|
||||
个人资料读取三人审查固定 `GET /genealogy/app/auth/profile` 为 M01/M02/M03 共用的唯一接口 owner。首批只要求 canonical 11 位 `phone` 必填;`nickName/realName/email` 未设置时唯一省略,出现时必须是非空规范字符串,姓名最多 30、邮箱格式有效且最多 100。adapter 立即把明文手机号变为掩码和读屏标签,只输出固定页面模型;`userId/avatar/status` 等字段全部丢弃,因此它们的 int64/枚举不阻塞本批。受保护双导出仍是通用 `ObjectResult/RObject`,线上 `RAppProfileVo/AppProfileVo` 则无 required 和字段边界;匿名实测仍是 HTTP 200+业务 401,与文档 401 string 冲突。问题单 `API-PROFILE-READ-001`—`003` 与 `tests/profile-openapi-contract.ps1` 已建立;门禁通过前不把 live-only 字段接进页面。
|
||||
|
||||
通知三人审查把读取与写入拆成两个原子批次。读取唯一使用 `GET /genealogy/app/notifications` 和 `GET /genealogy/app/notifications/unread-count`:列表必须完整返回当前账号最多 200 条活动通知并按最新优先,未读数精确统计同一集合;页面只消费完整纯文本标题/正文、带时区时间和 `READ/UNREAD`,所有服务端 ID、发送者与业务目标都不进入公开页面模型。N02 没有详情接口,首批只能以当前内存 `generation+ordinal` 的 `snapshotKey` 打开完整快照,重启、账号切换或成功刷新后的旧 key 均提示返回消息中心重新打开。已读写入另由两个 POST 持有,要求无损字符串 `notificationId`、当前账号幂等、read-all 截止点和并发新消息语义;通过前删除伪本地已读而不发请求。当前受保护双导出的列表仍是通用模型且没有未读数路径,线上模型无 required/枚举/容量,所有 ID 仍为 int64,匿名实测又是 HTTP 200+业务 401。问题单 `API-NOTIFICATION-READ-001`—`003`、`API-NOTIFICATION-STATE-001`—`003` 以及两项失败合同已经建立,均不以猜测代码绕过。
|
||||
|
||||
M02 资料写入三人审查保留现有 `PUT /genealogy/app/auth/profile` 作为唯一 App owner,但要求后端把它正式定义为原子 dirty-only merge,而非全资源替换或依赖 DTO 惯例猜测。请求只允许脏的 `nickName/realName/email`:省略保持,昵称出现时必须非空,真实姓名/邮箱的精确空串表示清空,null、纯空白和边界空白非法;成功返回完整 canonical `RAppProfileVo`,清空后的可选字段仍省略。并发沿用 T01 的单一版本模型:`AppProfileVo.profileVersion` 是 opaque string,PUT 必带同形状 `If-Match`,旧版本返回 409 `PROFILE_VERSION_CHANGED`,H5 CORS 同步允许该 header。受保护双导出还是旧 `ProfileUpdateBody → RObject`,线上则是无 required/merge/version/security 的 `AppProfileUpdateBody → RAppProfileVo`;当前页面还把同一 fixture 名称同时填入昵称和真实姓名,并用 500ms 定时器伪造本地校验。问题单 `API-PROFILE-UPDATE-001`—`004` 与 `tests/profile-update-openapi-contract.ps1` 已建立;GET 门禁和本门禁通过前不接写入。
|
||||
|
||||
M10 退出三人审查固定 `DELETE /genealogy/app/auth/logout` 只撤销请求中 bearer 所属的当前设备凭证族;同账号其他设备保持登录,“全部设备退出”必须另立接口。活动、已撤销和已过期但仍可验证为本 client 签发的历史凭证重复 DELETE 都返回同一个 200 `RVoid`,且成功后旧 token 对任何受保护接口均不可用;伪造、格式非法或 client 不匹配才返回 typed 401 拒绝。客户端唯一 `logoutCoordinator` 在同一同步临界段捕获 A 的 token/clientid、清本地 token/家谱上下文并 bump epoch、用显式快照创建不绑定 M10 生命周期的请求,然后立即 `goRoot(A01)`;异步回调永不再次 clear,避免误删随后登录的 B。线上 endpoint 虽存在,却无 security/clientid、required RVoid、范围/幂等/复用反例和 JSON/no-store;问题单 `API-LOGOUT-001`—`003` 与 `tests/logout-openapi-contract.ps1` 已建立。任何远端失败都不恢复本地 token,只区分“已从本机退出;服务器撤销已确认/未确认/未能撤销”。
|
||||
|
||||
M04 改密三人审查否决当前登录/注册/找回/改密共用的静态 32 个十六进制字符 MD5 wire:它是可直接重放的密码等价物,也使服务端无法执行真实新密码策略。唯一生产目标是四条入口原子迁移到 HTTPS 中的 raw `writeOnly` 密码;当前密码与登录兼容 1—64 Unicode code point,新密码统一 15—64 code point、NFC、允许空格与 Unicode、无组成规则,并由服务端执行常见/泄露密码 blocklist、账号限速和带独立盐的自适应慢哈希。M04 当前密码就是重新认证,TAC 不替代身份;用户明确要求的 A01/A04/A05 TAC 仍由原认证门禁持有。严格 200 前服务端必须原子落密、提升 `credentialEpoch` 并撤销包括调用者在内的所有 access/refresh session,客户端清本机回 A01;网络、超时、畸形响应或 5xx 也是结果未知,同样清本机且不自动重试。问题单 `API-PASSWORD-001`—`005` 与 `tests/password-change-openapi-contract.ps1` 已建立;门禁通过前 M04 保持诚实本地预览。
|
||||
|
||||
M05 换绑三人审查否决“活动 bearer+新号验证码”直接改号:这只能证明控制新号码,不能证明当前账号本人。唯一生产流程是活动 session、最终 PUT 内 raw `currentPassword` 重新认证、`APP_PHONE_CHANGE` TAC 和新号严格 6 位 OTP;不强制旧号 OTP,避免用户丢失旧号时永久锁死,但成功事务必须持久写入旧号安全通知 outbox。标准 OpenAPI 无法按公共发码 body 的 `sceneCode` 条件化鉴权,因此采用独立且强制 SaToken 的 `POST /genealogy/app/auth/phone/sms/code`,scene 由路径固定;公共 `/auth/sms/code` 删除 `APP_PHONE_CHANGE`,两个 operation 仍复用同一 OTP 生成、限速和存储 owner。最终 `PUT /auth/phone` body 只含 `currentPassword/phone/smsCode`,在同一事务内消费 OTP、执行号码唯一约束、更新号码、提升 `credentialEpoch`、撤销包括当前在内的全部 access/refresh session并写通知 outbox,严格 200 返回 `RVoid`。无密码账号返回 `STEP_UP_UNAVAILABLE`,不能降级;最终 PUT 的超时、5xx、畸形响应或进程终止均按结果未知清本机回 A01且不自动重试。问题单 `API-PHONE-001`—`005` 与 `tests/phone-change-openapi-contract.ps1` 已建立;M04 raw-password 门禁、认证/TAC 门禁和本门禁通过前,M05 保持诚实本地预览。
|
||||
|
||||
G03 创建链路三人反向质询后否决“先建空谱、再写始祖”的 `ROOT_REQUIRED` 两写方案:当前产品没有跨会话保存空谱的需求,而两写会凭空增加半成品配额、可见性、恢复、取消、过期和第二次未知结果。唯一生产目标是第一步只在本页收集资料,最终按钮以 `AppGenealogyBootstrapBody` 一次原子创建家谱、OWNER 成员关系、带数据库权威标记的唯一一世始祖、READY 状态和幂等回执;通用人物 collection、人物 DELETE 与 parents mutation 必须阻断第二根、删除根和重挂父母,人物 PUT 对根的可编辑白名单精确只有 `name/sex/birthDate/biography`,其他字段全部 typed 422。首次 POST 先以短控制事务认领 PENDING/digest/fencing,再由业务事务完成全部写入,任一步失败全回滚并 CAS 终态;结构化转换只允许 `ABSENT→PENDING→SUCCEEDED/FAILED_NO_COMMIT` 且终态不可变,FAILED 机器保证零领域写与零 quota,PENDING 的 `resolveBy<=claimedAt+2 分钟`。`GenealogyBootstrapOperationKey` 由 13 位服务端判定的 issuedAt 毫秒与至少 128 位 CSPRNG 组成,`acceptUntil=issuedAt+10 分钟`、最大未来偏差 5 分钟,并由 600/300 秒扩展锁定;operation-status GET 无 body、纯读且只返回显式 discriminator 的三个状态,截止前 404 保持同 key,截止后无记录按 key 计算 FAILED 而不写墓碑。请求使用受鉴权 `/genealogy/app/region/search`、词法 `GenealogyRegionCode`、服务端 `selectable` 和统一 `GenealogyAccessPreset=MEMBER_ONLY/PUBLIC_APPLY`,同版删除公共地区旧路由,并递归清除组合 schema 中的 `visibility/joinMode` 及旧 create/update DTO;G11 在本批只取得共享字段形状,其 If-Match、版本/CAS、权限刷新与结果未知仍是独立门禁。客户端仅持久 `{sessionEpoch,operationKey,startedAt}`;`IDEMPOTENCY_KEY_REUSED` 必须进入 fatal/quarantined,禁止查询或安装 status、自动换 key,只有用户明确放弃才能清理;其他分支按可证明未提交与 unknown 分离。成功固定按 receipt→`/mine` cache→context→G05 收口,context/导航失败只重试本地闭环。问题单 `API-G03-001`—`005`、完整双源深比较、后端 `tests/g03-bootstrap-openapi-contract.ps1` 和实际执行状态机套件的客户端 `tests/g03-bootstrap-client-release-gate.ps1` 已建立;G03 两门禁转绿仍不能绕过家谱工作区读取门禁、聚焦/全量回归与 MuMu 原生验收,全部通过前保留诚实本地预览,不接 `appApi.createGenealogy`。
|
||||
|
||||
当前 JSON 的 SHA-256 为 `87db1dc148c5e6e877815aff7b3a7fc7c7eca95a2cec50a88b42f3908961f31a`,YAML 的 SHA-256 为 `8964cd583ce172425b63bbfd802f7eb587eb3641eadfd6f9d3b264faa8090c6c`。两份文件均已被 Git 跟踪,当前相对 HEAD 无差异;本轮没有改写、格式化或覆盖接口导出。
|
||||
|
||||
## 必须保护的用户文件
|
||||
|
||||
- `APP.openapi.yaml`、`APP.openapi.json`:后端接口离线源快照,只接受同一后端版本的双导出替换,不得手工修改。
|
||||
- `static/tac/`:当前共 5 个文件,其中 4 个是后端提供且不可改写的供应商文件,第 5 个是项目适配器 `static/tac/js/jiapu-tac-adapter.js`;A01/A04/A05 已形成真实运行时消费者,文件集合与供应商哈希由 `tests/auth-tac-integration-contract.ps1` 唯一保护。
|
||||
- 当前五份中文治理文档:长期唯一入口,随已验证进度同步更新,不另建平行计划。
|
||||
|
||||
任何清理批次都不得覆盖、恢复或删除以上文件。
|
||||
|
||||
@@ -59,41 +121,111 @@ Apifox 是接口合同的唯一源头。`APP.openapi.json` 用于自动扫描和
|
||||
- 旧 A01 固定画布、PSD、Photoshop、v2 按钮和旧卷轴入口已经三人一致退役;没有通过重建旧预览制造假绿。
|
||||
- schema v3 注册表已经完整覆盖当前 `static/assets`:认证直接资产、其余应用直接资产、共享卷轴、六张长页面背景和 G01 空态边框各有唯一 owner;旧 G01 候选与模块背景清单及注册例外已经删除。
|
||||
- `design-pipeline/manifests/shared-scroll-skins-v3.json` 只拥有母版、处理、物理输出和质量规则,不再拥有页面槽位或 `uni-app mode`。
|
||||
- `tests/runtime-assets-contract.ps1` 精确闭合 `79` 个正式输出、物理文件和源码消费者;`tests/retired-asset-removal-contract.ps1` 防止旧候选、旧图标、TAC 残件与旧构建入口回归。
|
||||
- 阶段 0 验收时,`tests/runtime-assets-contract.ps1` 精确闭合 `79` 个正式输出、物理文件和源码消费者;导航任务 3 又删除了退役通用页面唯一虚构消费的两张 notification frame,当前正式输出为 `77` 个,并由 `tests/retired-module-page-contract.ps1` 防止旧入口与孤立资产回归。
|
||||
- 设计管线当前为 Node `25/25 PASS`、Python `19/19 PASS`;四张共享卷轴、六张长背景和 G01 空态边框均可真实重建并通过质量审计。
|
||||
- 质量报告只含工作区相对路径;确定性测试证明相同输入连续两次得到相同字节哈希与报告。
|
||||
- 六张长背景与 G01 空态边框迁移后的可见像素哈希逐张等于迁移前正式图;连续两次构建的文件 SHA-256 也完全一致。旧 Sharp 单用途依赖和专用构建器已删除。
|
||||
- 已删除 `28` 个无运行时消费者的 `static/assets`、`3` 个旧 `static/icons`、`4` 个未接入业务的 TAC 文件和 `8` 个候选或伪母版;两张用户批准的 A01 选型图作为 `reference-only / non-runtime` 视觉锚点保留。
|
||||
- 阶段 0 验收时的 MuMu 证据:`emulator-5554` 当时在线,ADB 设备字段为 `model:SDY_AN00`,系统型号为 `SDY-AN00`,物理尺寸 `720×1280`,density `320 dpi`。本轮原生截图逐张打开复核了 A01、G01、T01 和 F01,未见缺图、透明错误、异常裁切或样式回退;复核结束后已回到 A01 页面,未启动、关闭或调整模拟器。
|
||||
- `docs/接口与页面映射总表.md` 已按 `pages.json` 建立 `52/52` 活动页面映射;除 A04 与 T01 专项结论外,其余具体接口明确标记为“待对应业务阶段 OpenAPI 审查”。
|
||||
- 第一轮六项产品优化已经完成:G01/G03/G06 统一读取 `onLoad(query)`;AppDialog 统一安全区与高度预算;AppTabbar/GenealogyCard 补齐可访问点击语义;G10 拒绝原因建立错误关联与失败聚焦;A04/A05/M04 统一消费唯一密码策略;T01 可在视图漂移后精确回到当前成员。
|
||||
- `docs/接口与页面映射总表.md` 已按 `pages.json` 建立 `52/52` 活动页面映射;A04、G 系列第一轮、T01 和新线上 OpenAPI 差异已有专项结论,其余具体接口仍标记为“待对应业务阶段 OpenAPI 审查”。
|
||||
- 第一轮六项产品优化已经完成:G01/G06 统一读取受控查询参数,G03 在本轮进一步删除了无业务参数的 `onLoad`;AppDialog 统一安全区与高度预算;AppTabbar/GenealogyCard 补齐可访问点击语义;G10 拒绝原因建立错误关联与失败聚焦;A04/A05/M04 统一消费唯一密码策略;T01 可在视图漂移后精确回到当前成员。
|
||||
- 后续产品阶段按顺序为导航栈语义统一、T01 大规模世系树、短信验证码完整状态机、跨页面领域数据持久化、全局文字层级和无障碍第二轮;不得混合实施。
|
||||
- 文档权威迁移已删除 `198` 个旧 Markdown、`35` 张旧 H5/静态设计图、`4` 个只保护旧资料的测试和 `.superpowers/sdd` 的 `12` 个过程文件;阶段 0 临时清单随后也已删除,当前 `docs/` 只保留 `5` 个长期中文 Markdown 入口。
|
||||
- 浏览器截图链已把 G03、G06、G08—G12、T06、F02、N01 的有效状态断言迁回各自现有运行时合同;G01、T03—T08、T07 三个有价值的浏览器测试只剥离截图写盘,继续验证状态、滚动、溢出与交互。
|
||||
- 已删除固定 Chrome 截图助手、联系表、8 个只保护截图助手的合同,以及迁移后重复的 N01/A02 合同;`tests/mumu-visual-acceptance-boundary-contract.ps1` 现在唯一约束“仓库不维护自动截图证据链、最终视觉通过只来自 MuMu”。
|
||||
|
||||
当前全量 PowerShell 合同为 `119/119 PASS`;五个核心验证、密码策略 Node 冒烟、设计管线 Node `25/25`、Python `19/19`、四份资产清单验证和共享卷轴质量验证均通过。根 `tests/` 现有 `29` 个 Node 文件,其中唯一不依赖 H5/CDP 服务的密码策略冒烟已实际执行;其余运行时文件只在具备 `127.0.0.1:9222` 调试页时执行,不能用语法检查冒充运行通过。
|
||||
当前物理库存为 `140` 个 PowerShell 合同、`47` 个 Node 文件和 `4` 个 JSON 合同数据文件。最新 fresh 结果为 PowerShell `126/140` 通过;其余 14 项不是可删除的普通回归,而是保留真实外部或发布阻塞的 `tests/lineage-openapi-contract.ps1`、`tests/auth-tac-openapi-contract.ps1`、`tests/genealogy-workspace-openapi-contract.ps1`、`tests/g03-bootstrap-openapi-contract.ps1`、`tests/g03-bootstrap-client-release-gate.ps1`、`tests/help-center-openapi-contract.ps1`、`tests/profile-openapi-contract.ps1`、`tests/profile-update-openapi-contract.ps1`、`tests/notification-read-openapi-contract.ps1`、`tests/notification-read-state-openapi-contract.ps1`、`tests/logout-openapi-contract.ps1`、`tests/password-change-openapi-contract.ps1`、`tests/phone-change-openapi-contract.ps1` 和缺少 MuMu 人工证据时必须失败的 `tests/auth-android-accessibility-release-gate.ps1`。十四个输出分别为 `LINEAGE-OPENAPI-CONTRACT BLOCKED`、`AUTH-TAC-OPENAPI-CONTRACT BLOCKED`、`GENEALOGY-WORKSPACE-OPENAPI-CONTRACT BLOCKED`、`G03-BOOTSTRAP-OPENAPI-CONTRACT BLOCKED`、`G03-BOOTSTRAP-CLIENT-RELEASE BLOCKED`、`HELP-CENTER-OPENAPI-CONTRACT BLOCKED`、`PROFILE-OPENAPI-CONTRACT BLOCKED`、`PROFILE-UPDATE-OPENAPI-CONTRACT BLOCKED`、`NOTIFICATION-READ-OPENAPI-CONTRACT BLOCKED`、`NOTIFICATION-READ-STATE-OPENAPI-CONTRACT BLOCKED`、`LOGOUT-OPENAPI-CONTRACT BLOCKED`、`PASSWORD-CHANGE-OPENAPI-CONTRACT BLOCKED`、`PHONE-CHANGE-OPENAPI-CONTRACT BLOCKED` 和 `ANDROID-AUTH-ACCESSIBILITY-RELEASE BLOCKED`。Node 语法 `47/47` 通过,纯 Node 冒烟 `19/19` 通过,活动 Vue 脚本模块语法 `64/64` 通过;导航源码扫描保持 `MIGRATION-DEBT=0`。依赖 `127.0.0.1:9222` 调试页的浏览器运行时文件本轮只做语法检查,没有执行;这不能冒充浏览器行为或 MuMu 原生视觉通过。
|
||||
|
||||
清理结束后,`static/` 精确保留 `79` 个正式文件、共 `70,414,397` 字节;`tmp/`、`unpackage/` 和 `design-pipeline/generated/` 均不存在。三位审查者确认删除的三类临时输出分别为 `94`、`188` 和 `3` 个文件;正式生成资产仍由清单和构建器恢复,质量报告不作为长期资料保留。
|
||||
阶段 0 清理结束时,`static/` 的正式基线为 `79` 个文件、共 `70,414,397` 字节。任务 3 退役两张孤立通知 frame 后,正式 `static/assets` 为 `77` 个文件、共 `69,093,742` 字节;`static/tac/` 当前 5 个文件、共 `68,190` 字节,因而当前 `static/` 物理库存为 `82` 个文件、共 `69,161,932` 字节。4 个供应商文件保持原字节并由哈希合同保护,新增适配器是唯一项目映射层;TAC 已进入 A01/A04/A05 业务运行时,但不属于可重建视觉资产注册表。`tmp/`、`unpackage/` 和 `design-pipeline/generated/` 均不存在;正式生成资产仍由清单和构建器恢复,质量报告不作为长期资料保留。
|
||||
|
||||
## 当前规划结论
|
||||
|
||||
### 导航栈
|
||||
|
||||
- 52 个活动页面中只读统计为 `navigateTo 59`、`navigateBack 11`、`redirectTo 14`、`reLaunch 7`,活动页面合计 91 次;活动组件另有 4 次,封存 A06 另有 2 次;没有 `switchTab`。
|
||||
- 迁移前 52 个活动页面只读统计为 `navigateTo 59`、`navigateBack 11`、`redirectTo 14`、`reLaunch 7`,活动页面合计 91 次;活动组件另有 4 次,封存 A06 另有 2 次。任务 3—9 已依次迁移共享组件、认证、G、T、F、R、N/M,任务 10 删除最后一个无消费者旧表单组件并关闭门禁。当前 pages/components 中 `navigateTo`、`navigateBack`、`redirectTo`、`reLaunch`、`getCurrentPages` 和业务页面路径字面量均为 `0`,`switchTab` 全项目为零;五种 Uni 导航调用和页面栈读取只允许由 `utils/navigation.js` 持有,业务路径只允许由 `utils/navigation-routes.js` 持有。
|
||||
- MuMu 已复现 F01→F03 返回后残留两个 F01、A01→A04→登录后残留两个 A01、T03 同路由连续叠页,以及栈深为 1 时 F03 错回 G01。
|
||||
- 三人终选两个唯一所有者:`utils/navigation-routes.js` 持有 52 条路由、父页参数映射和结果操作枚举,`utils/navigation.js` 持有五种 Uni 导航 API、六个公开语义方法、一次性结果与统一返回优先级。
|
||||
- G01、F01、M01 保持自定义 Tab 根页;A01 是认证根页。T03 只保留一个原生页面实例,亲属浏览使用页内成员轨迹。
|
||||
- 当前尚未新建这两个模块,也没有迁移任何页面;实施从 `tests/navigation-routes-contract.ps1` 的预期红灯开始。
|
||||
- 三人终选并已实现两个唯一所有者:`utils/navigation-routes.js` 持有 52 条路由、父页参数映射和结果操作枚举,`utils/navigation.js` 持有五种 Uni 导航 API、五个公开导航语义方法、一次性结果与统一返回优先级;没有真实替换边,因此不公开替换方法。
|
||||
- G01、F01、M01 保持自定义 Tab 根页;A01 是认证根页。T03 只保留一个原生页面实例,初始路由 `personId` 是不可变宿主页身份,亲属浏览使用页内成员轨迹和可变活动成员;T05 本地预览离开使用 `goBack()`,不会把活动成员误写成宿主页路由身份。
|
||||
- 任务 1—10 已完成测试先行和静态实现;一次性结果绑定真实目标路由与业务参数上下文,只能由当前真实栈顶目标页消费。52 个活动路由、共享组件、四个根语义以及认证、G、T、F、R、N/M 系列均已迁移;通知目标只能由本地类型白名单映射,未知或越权目标失败关闭。未接真实写接口的页面只能形成明确的本地预览或硬关闭,不再展示伪保存、伪邀请码、伪订单或伪安全结论。任务 4—10 的 MuMu 原生流程复核仍因本轮禁止触碰模拟器而待执行,静态零债务不能替代该验收。
|
||||
|
||||
### T01 大规模世系树
|
||||
|
||||
- 当前默认 6 人数据已存在 103→106 断线;普通节点实际半高与全局常量相差约 4rpx;10×12 压力数据约 45/54 个父分组会断线,而旧测试只检查线段数量。
|
||||
- 当前布局最坏为 `O(G×N)`,单人一代时接近 `O(N²)`;页面、mock 和 OpenAPI 分别使用 `parentId`、无关系字段和递归 `children/spouses`,无法直接对接。
|
||||
- 此前默认 6 人数据中的 103→106 断线已通过唯一成员 owner 与字符串 `parentId` 修复;普通节点实际半高与全局常量仍相差约 4rpx,10×12 压力数据约 45/54 个父分组仍会断线,而旧测试只检查线段数量。
|
||||
- 当前布局最坏为 `O(G×N)`,单人一代时接近 `O(N²)`;页面与 mock 已统一当前阶段的 `id/parentId` 夹具,但 OpenAPI 仍使用递归 `children/spouses`,无法直接对接未来规范图窗口。
|
||||
- 产品终选“焦点成员渐进窗口+独立全谱概览”,初始上二代/下二代;主世系为骨架,配偶并排,每段家庭关系有独立联合点,子女从对应联合点向下。
|
||||
- 技术终选视口大小的单 Canvas 同画节点和边;纯 JavaScript 按“规范化→严格校验→可见投影→确定性布局→Scene/空间索引→相机”处理,DOM 只保留页头、工具条、两档抽屉、搜索、概览和无障碍线性列表。
|
||||
- 当前 OpenAPI 的 v1 `/lineage/tree` 仍是递归 `LineagePersonTreeView[]`,并缺少 overview、locator、稳定关系寻址和树版本并发合同。后端问题单 `API-T01-001` 已固定四条 `/genealogy/app/v2/...` 路径、FOCUS/BOUNDARY 查询、`schemaVersion/treeVersion`、EMPTY/POPULATED 空谱判别、窗口入口与全谱根分离、严格匿名节点、以 `relationshipKind` 判别的可寻址伴侣/父子关系、空 PATCH 错误码和 Scene 原子版本;在用户重新导出通过新合同前,T01 客户端不得开始实施。
|
||||
- 当前规划轮没有改写 OpenAPI 文件,没有启动、关闭或调整 MuMu,也没有修改页面与业务代码。
|
||||
- 本地旧导出和 2026-07-22 线上 OpenAPI 都只有 v1 `/lineage/tree`,仍是递归模型,并缺少 overview、locator、稳定关系寻址和树版本并发合同。最新线上文档的 `722` 条路径中没有任何 `/genealogy/app/v2/`,`507` 个模型中也没有新图辨识字段,四个稳定业务码同样缺失。后端问题单 `API-T01-001` 已固定四条 v2 路径、FOCUS/BOUNDARY 查询、`schemaVersion/treeVersion`、EMPTY/POPULATED 空谱判别、窗口入口与全谱根分离、严格匿名节点、以 `relationshipKind` 判别的可寻址伴侣/父子关系、空 PATCH 错误码和 Scene 原子版本;`tests/lineage-openapi-contract.ps1` 已建立严格红灯,在同版本线上文档与双导出通过前,T01 客户端不得开始任务 12。
|
||||
- 当前实施轮没有改写 OpenAPI 文件,也没有启动、关闭或调整 MuMu;已完成导航共享所有者、共享组件/根页头部语义、退役死入口、认证、G 系列、T 系列和 F 系列导航合同,以及树成员与家族内容夹具统一。认证请求代码已对准真实端点,但运行模式仍失败关闭;G/T/F 系列真实写接口与 T01 新图尚未接入。
|
||||
|
||||
### TAC 认证与无障碍安全边界
|
||||
|
||||
- `utils/auth-verification.js` 是当前认证场景、4 位短信码和服务端票据形状的唯一客户端 owner;生产目标由后端 `SmsCodeSecret` 和 `tests/phone-change-openapi-contract.ps1` 锁定为严格 6 位,门禁通过时必须一次替换所有活动消费者并删除旧 owner 的四位规则。`components/TacVerification.vue` 是验证浮层与 renderjs 生命周期 owner;`static/tac/js/jiapu-tac-adapter.js` 是 TianAi challenge/proof/verify 映射 owner;`utils/api.js` 是严格 HTTP 200 envelope、15 秒超时、离页中止、认证会话写入和反馈 wire payload 的唯一 owner。旧认证专用请求控制器入口、占位验证、伪验证码、宽松令牌兼容和 M07 假提交均已删除。
|
||||
- A01 默认短信登录;密码登录因 `API-AUTH-TAC-001` 保持可见但不可用。A04/A05 都只在发送短信前验证一次,并在手机号改变后使旧验证码上下文失效;成功、失败、取消、重复回调、空响应、非 JSON、超时、离页和返回键均有静态或纯运行时合同。短信发送与重设密码的 `RVoid` 没有声明 `data` 必填,客户端因此只要求合法 HTTP 200 与整数成功 `code`,并把“省略 data”或 `data:null` 都精确归一为 `null`;登录、注册和 challenge 等有实体响应仍强制 `data`。
|
||||
- 当前客户端浮层已补齐对话框命名、说明关联、初始聚焦、Tab 圈定、Escape/Android 返回、焦点恢复、原生刷新与关闭按钮、48px 目标及小视口内部滚动;这只是客户端壳层预检,不代表第三方滑块本身可由 TalkBack 或键盘完成。`tests/auth-android-accessibility-release-gate.ps1` 要求 MuMu 原生证据与非拖动等价验证方式,证据缺失时固定输出 `ANDROID-AUTH-ACCESSIBILITY-RELEASE BLOCKED`。
|
||||
- 三人交叉质询后的唯一方向是“同一验证中心、同一短时单次 `validToken`、供应商无关的服务端决策”,永久禁止 `accessibility=true`、跳过 TAC 或无票据发短信。中国大陆非交互风控供应商只进入限时 POC,必须在真实 UniApp Android WebView 中证明 TalkBack、外接键盘和 Switch Access 无焦点陷阱且误杀、防刷、弱网与故障指标达标,不能预先写成无障碍完成。已安全绑定设备只能作为加权信号;供应商不确定或不可用时进入支持文字/中继的可审计人工兜底或稍后重试,绝不 fail-open;音频验证码仅为后续独立 POC 候选,不能作为 P0 或唯一替代。
|
||||
|
||||
### 领域上下文与 M07 反馈
|
||||
|
||||
- `utils/genealogy-context.js` 只接受无边界空白的非空词法字符串 ID,并持有独立失效标记;`utils/session.js` 在损坏令牌、退出和账号令牌变化时同步清理 ID 与标记。首次且没有历史选择或标记时可确定选择首个可用家谱;针对调用方提供的新列表,历史选择消失或显式目标不可用时写入标记并由 G01 要求用户明确重选,跨重载也禁止静默串谱。真实后台撤权能否被及时发现仍取决于后续 workspace 的 onShow/事件刷新。
|
||||
- `appApi.submitFeedback` 是反馈提交唯一 owner:只接受 `feedbackContent` 必填、`feedbackType/contactInfo` 可选的三个字符串字段,trim 后删除空选填值;调用方不能关闭认证头。remote 精确 POST `/genealogy/app/feedback`,只认 HTTP 200 且 `code` 为整数成功码的 envelope;反馈响应没有声明 `data` 必填,页面也不消费返回实体。mock 固定失败关闭且不调用网络。
|
||||
- M07 提交中禁用输入并使用提交前快照;成功后保留回执内容并禁止原样重复提交,请求期若仍有迟到输入则明确区分“上一份已提交”和“当前修改未提交”。超时、断网、意外 2xx/3xx、HTTP 408/5xx 或响应无效进入 `uncertain`,同一快照禁止重提;unknown 回流也会重新比较当前表单,迟到输入保持可提交而不冒充旧快照。只有确定拒绝才允许重试。`tests/m07-feedback-state-runtime-smoke.js` 已直接执行这些页面状态;真实服务联调仍受认证远端门禁约束。
|
||||
|
||||
### 家谱工作区远端门禁
|
||||
|
||||
- 三人反向质询后没有要求后端把 `AppGenealogyVo` 全部字段设为必填,也不把缺少 `security` 注解或文档媒体类型 `*/*` 单独宣称为数据泄漏。首批只消费 `genealogyId/genealogyName/canView/canManage/canEditContent/roleType`;前五项需明确类型并必填,`roleType` 还需非空正式枚举。地点、堂号、人数和简介可选并由页面诚实降级,未知额外响应字段允许忽略。
|
||||
- JSON 响应身份必须使用非空词法字符串。仅在客户端拒绝 unsafe number 虽可避免串谱,却会让 OpenAPI 合法的 int64 用户永久不可用,不能作为普遍上线方案;URL path 在 wire 上本就是文本,不机械要求为了同一问题改类型。
|
||||
- `/mine` 必须以必填 `canView` 或等价、可自动验证的投影保证只把当前账号仍可查看的家谱送入 context;G01 只有成功取得新列表才 reconcile。网络、超时和 5xx 保留旧现场并显示错误,明确撤权/无权/不存在才写 tombstone。G05 使用 `/overview`,取消迟到请求并在新身份加载前清掉旧数据,远端失败绝不回退 fixture 角色。
|
||||
- OpenAPI 注解修复后仍须用有效账号执行无凭证、跨账号、撤权、删除、5xx、畸形 JSON 和取消反例;当前无令牌 HTTP 200+业务 `code=401` 的承载方式可以保留,也可改规范 HTTP 状态,但文档、运行时 validator 与部署行为必须一致。
|
||||
|
||||
### M06 帮助内容远端门禁
|
||||
|
||||
- 首批只使用 `/genealogy/app/help-articles` 一次取得完整正文;详情接口、文章深链、封面、浏览量和业务 ID 均不属于 M06。adapter 只准输出 `{category,title,content}` 与当前响应生命周期的展示键,必须显式丢弃 `helpId/status/sortOrder/viewCount/coverOssId/remark`。
|
||||
- 响应 envelope 的 `code/data` 和每行 `helpCategory/helpTitle/helpContent` 必须 required;三项均为非空字符串,分类是可直接展示标签,正文首版固定为纯文本。用户侧列表只返回已发布内容,数组顺序就是展示顺序;客户端不猜分类码、不解释 HTML/Markdown。
|
||||
- 搜索与筛选只作用于已投影数组;筛选后不得重新编号。搜索、分类和刷新先收起正文,新请求以 controller+generation 拒绝迟到响应,离页取消请求;空列表、搜索无结果、加载失败和认证失效必须分别表达。
|
||||
- M06 现有分类和问题标题使用 `<view role="button">` 且触控高度不足;真正接线时改为原生按钮语义,补 `aria-pressed/aria-expanded/aria-controls`、状态播报、重试和至少 44dp 目标。源码审查不能替代 MuMu 的系统字号、TalkBack、焦点与视觉验收。
|
||||
|
||||
### 个人资料读取远端门禁
|
||||
|
||||
- 唯一 profile adapter 在 mock/remote 两种模式都产出同一窄模型;运行模式必须经 `resolveRuntimeMode()` 校验,错误配置不得静默回 fixture。remote 使用严格 envelope、15 秒超时、请求取消和 generation 防迟到,不建立跨账号缓存或经路由传递个人资料。
|
||||
- 原始手机号只在 adapter 局部验证 canonical 格式,输出仅含 `maskedPhone` 与“绑定手机号,尾号 xxxx”读屏标签;页面、日志、错误、缓存和路由都不得接触明文。可选昵称、实名、邮箱只有真正省略时规范为内部空串;出现 null、空白、超长或非法邮箱时整份失败关闭。
|
||||
- M01 的加载/失败只替换身份卡,服务菜单与底栏保持可用,并删除查询参数制造的假错误和“重试即成功”;通知 fixture 不得与真实身份混装为线上数量。M02 完成异步填表后才建立 dirty baseline,GET 不冒充 PUT 保存;M03 只让手机号行局部加载/失败,密码入口不受普通读取失败影响,并删除不属于账号资料的“创建者”角色。
|
||||
- 真正实施时原子替换禁止页面调用 API 的旧静态断言,使用 `AppLoading` 和原生按钮,补 M02 的 `aria-invalid/aria-describedby`、失败聚焦、状态播报、44dp 目标、长昵称换行和装饰图隐藏;MuMu 仍须验证系统字号、TalkBack、焦点与真实纹理对比。
|
||||
|
||||
### 通知读取与已读状态远端门禁
|
||||
|
||||
- 读取批次以无筛选列表作为当前账号完整活动集合 owner,`RListNotificationVo.data` 允许空但最多 200 条并按 `publishTime` 最新优先;`RNotificationUnreadCount.data` 为 0—200,精确等于同一活动集合中 `readStatus=UNREAD` 的数量。两个请求之间发生并发新消息时允许瞬时差异,客户端不得据此判错。
|
||||
- `NotificationVo` 的首批 required 字段为 `noticeTitle/noticeContent/publishTime/readStatus`:标题 1—50、完整正文 1—1000 且为纯文本,时间必须是带时区 RFC3339,状态只允许 `READ/UNREAD`。N01 摘要最多显示 160 个字素,N02 展示同一快照的完整正文;长文本必须换行,不解释 HTML、Markdown、URL 或服务端跳转字段。
|
||||
- 读取 adapter 只公开 `{snapshotKey,title,content,publishedAt,unread}`,key 由成功响应 generation 与映射前 ordinal 组成;不持久化、不使用裸序号,也不把 `notificationId/genealogyId/bizId/bizType/senderPhone` 暴露给页面。成功刷新原子替换 generation,退出和账号切换立即清空;N02 不能解析 key 时只提示“请返回消息中心重新打开”。
|
||||
- 首批删除通用“前往入谱审核”、详情目标按钮和从消息字段猜路由;后端将来只有提供闭合的 `bizType → route key+必填词法参数+权限/失效语义` 字典后才可独立恢复 CTA。M01/G01 共同消费未读数 owner,文案统一为“未读消息”,可见角标封顶 `99+`,读屏仍播报真实数量。
|
||||
- 写批次通过前,打开 N01/N02、点击“标记已读”或“全部已读”都不得只改本地 clone。写合同通过后,唯一通知 controller 才能私有保留 1—128 位 URL-safe opaque `notificationId`;页面仍只持有 `snapshotKey`。单条和全部已读必须对当前账号幂等;read-all 只覆盖服务端接收时已存在的活动通知,并发新消息保持未读;超时或结果未知时重新读取列表和计数收敛。
|
||||
- 两个失败门禁分别是 `tests/notification-read-openapi-contract.ps1` 与 `tests/notification-read-state-openapi-contract.ps1`。当前分别输出 `NOTIFICATION-READ-OPENAPI-CONTRACT BLOCKED` 和 `NOTIFICATION-READ-STATE-OPENAPI-CONTRACT BLOCKED`;后端同版本双导出、有效账号反例和 MuMu 状态矩阵完成前不能宣称通知闭环上线。
|
||||
|
||||
### M02 个人资料写入远端门禁
|
||||
|
||||
- PUT 请求体唯一 owner 固定为关闭额外字段的 `AppProfileMergeUpdateBody`,只能包含 1—3 个真正脏的 `nickName/realName/email`。昵称出现时为去边界空白的 1—30 字符;真实姓名和邮箱的精确 `""` 是唯一清空命令,非空真实姓名 1—30、邮箱 1—100 且格式有效。省略表示保持,null、纯空白、边界空白、空 body、头像/性别/生日或任意额外字段全部早失败。
|
||||
- `AppProfileVo` 新增 required `profileVersion`,形状为 1—128 位 URL-safe opaque string;写请求只在 required `If-Match` header 携带,不在 body 建第二版本字段。服务端按当前账号和租户原子 CAS,成功返回含新版本的完整 canonical profile,旧版本固定 409 `PROFILE_VERSION_CHANGED`;相同字段集重复执行不产生通知、审计之外的额外业务副作用。
|
||||
- M02 首次 GET 完成后才建立 baseline;保存时冻结三个输入和提交快照,成功用响应回填并重置 baseline。确定失败保留草稿;超时、断网、408/5xx 或畸形成功响应属于结果未知,先 GET 逐项核对本次脏字段,匹配则确认成功、仍为旧值才允许重试、第三值进入冲突,不盲目重复 PUT。
|
||||
- session generation 是账号隔离边界:账号切换、退出和页面卸载中止等待、清空未持久草稿并拒绝旧账号迟到响应;取消 RequestTask 不等于服务端未写。真实姓名、邮箱、提交 payload 和版本不得进入日志、路由、持久缓存或遥测。
|
||||
- 当前假定昵称必填,所以旧账号昵称省略时可以只改其他字段,但不能把已有昵称清空。头像选择、相册权限、OSS ID、性别和生日全部留到独立批次;当前 `<view role="button">` 假头像动作在写入实施时删除。邮箱没有验证与送达合同,页面不得继续承诺“用于接收通知”。
|
||||
- `tests/profile-update-openapi-contract.ps1` 当前输出 `PROFILE-UPDATE-OPENAPI-CONTRACT BLOCKED`。后端关闭 `API-PROFILE-UPDATE-001`—`004`、同版本双导出通过、读取 owner 可用、有效账号/CORS 反例和 MuMu 表单矩阵完成前,M02 保持诚实本地校验而不发写请求。
|
||||
|
||||
### M10 当前设备退出远端门禁
|
||||
|
||||
- 后端 DELETE 只撤销当前请求 bearer 及同一可续签凭证族;其他设备 token 保持有效。合法签发给同一 client 的 active/revoked/expired token 重复调用统一返回 200 RVoid,操作幂等且无额外副作用;成功后用旧 token 调 profile 等受保护接口必须失败。非法或 client 不匹配返回 `RLogoutRejected`,业务码只允许 `TOKEN_INVALID/TOKEN_CLIENT_MISMATCH`,不能冒充远端已撤销。
|
||||
- operation 必须 required SaToken 与非空 clientid、禁止 request body、200/401 使用 application/json 并带 `Cache-Control: private, no-store`;RVoid 的整数 code required,另声明 400/429/500。线上仅有文字“需要登录”和 `*/*`,本地虽有 security/clientid 但 RVoid 无 required、只列 200,均未达到发布合同。
|
||||
- `logoutCoordinator` 是唯一跨页 owner:同步捕获 token/clientid 与 logoutEpoch,立即调用 session owner 清 token、家谱上下文和账号缓存并 bump epoch,再以显式 A 快照创建 RequestTask;整个同步段不 await,M10 页面拿不到 token。创建失败也不恢复;请求不绑定页面 controller,reLaunch/A01/M10 卸载不 abort。
|
||||
- coordinator 的公开内存态只含 `{attemptId,logoutEpoch,status}`,状态为 pending/confirmed/unconfirmed/not-revoked,不保存 token、请求头或错误 payload。A01 仅在 session 仍为空且 epoch 未变化时原子消费一次提示;B 登录后 A 的迟到结果直接丢弃。进程被杀允许丢提示,但旧 token 不落盘、不排队、不跨重启重试。
|
||||
- 唯一用户承诺始终是“已从本机退出”。200 显示服务器撤销已确认;network/timeout/408/429/5xx/畸形响应与 generic 401 显示未确认;typed 401/400/403 显示未能撤销。所有分支都留在 A01,不恢复 token、不返回 M10、不重新开放登录后页面;异步 callback/finally 绝不能再次 `session.clear()`。
|
||||
- `tests/logout-openapi-contract.ps1` 当前输出 `LOGOUT-OPENAPI-CONTRACT BLOCKED`。同版本双导出、两设备 token 隔离/复用反例、真实网络异常和 MuMu 退出提示完成前,只能称现有行为为本机退出,不能宣称服务端注销完成。
|
||||
|
||||
### M04 登录态修改密码远端门禁
|
||||
|
||||
- 密码 wire 是跨 A01 登录、A04 注册、A05 找回和 M04 登录态改密的单一合同。`PasswordLoginBody.password` 与 `PasswordChangeBody.oldPassword` 只引用 `CurrentPasswordSecret`;注册、找回和改密的新密码只引用 `NewPasswordSecret`。生产入口不再接受 MD5、十六进制摘要或两套兼容分支;确认密码只留客户端。
|
||||
- `CurrentPasswordSecret` 是原样、不 trim 的 1—64 Unicode code point;`NewPasswordSecret` 是 NFC 后 15—64 code point,允许空格、Unicode 和密码管理器粘贴,不强制字母/数字组成。服务端才是策略权威,必须执行常见/泄露密码 blocklist、当前密码验证、新旧不同、账号级限速和带盐慢哈希;页面校验只是即时提示。
|
||||
- PUT required SaToken、非空 clientid 与关闭额外字段的 JSON body;200/400/401/409/422/429/500 均为 typed JSON、`private, no-store`,429 带 `Retry-After`。409/422 用 `RPasswordChangeRejected` 区分 `CREDENTIAL_VERSION_CONFLICT/CURRENT_PASSWORD_INCORRECT/NEW_PASSWORD_SAME_AS_CURRENT/PASSWORD_POLICY_VIOLATION`,不解析 `msg`。
|
||||
- 唯一会话方案是 ALL:严格 200 前原子写入新 verifier、提升 credential epoch,并让所有设备/所有 client 的旧 access/refresh session 跨节点失效;不返回新 token,也不让当前 bearer 继续存活。200、401、409 和传输结果未知都清秘密与对应本机会话并回 A01;只有明确未写的 400/422/429 可清 `credentialChangeInFlight` 后留页。
|
||||
- 为关闭 PUT 已发出后进程被杀的窗口,session owner 在发送前只持久化 `{sessionEpoch,startedAt}` marker,禁止保存 token、密码、摘要或 body。冷启动发现同 epoch marker 时必须在任何账号缓存渲染前清会话并进 A01;新登录 bump epoch,使旧 marker 和迟到响应失效。客户端不自动重试、不建 operation-status;重新登录就是最小对账路径。
|
||||
- `tests/password-change-openapi-contract.ps1` 当前输出 `PASSWORD-CHANGE-OPENAPI-CONTRACT BLOCKED`。后端关闭 `API-PASSWORD-001`—`005`、原子重导双文件、密码登录 TAC 可用、两设备/并发/故障注入与 MuMu 无障碍矩阵完成前,M04 继续显示“不提交服务器”。
|
||||
|
||||
## 三人规则
|
||||
|
||||
@@ -107,15 +239,24 @@ Apifox 是接口合同的唯一源头。`APP.openapi.json` 用于自动扫描和
|
||||
|
||||
- 不执行 Git add、commit、push、restore、checkout 或 reset。
|
||||
- 不启动、关闭或调整 MuMu。
|
||||
- 不删除源码、接口导出、当前权威设计或用户改动。
|
||||
- 不删除活动业务源码、接口导出、当前权威设计或用户改动;只有三人以消费者、合同和资产清单共同证明已经退役的旧入口才可同轮删除。
|
||||
- 不因测试失败而删除测试。
|
||||
- 不因静态搜索无匹配而直接删除资产。
|
||||
- 不归档确认失效的旧内容,不保留兼容入口。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 按 `docs/家谱项目全量治理实施计划.md` 的任务 1 先写路由注册表失败合同,再实现两个导航唯一所有者。
|
||||
2. 认证、G、T、F、R、N/M 每批独立运行聚焦合同、全局响应式合同、编译审计和 MuMu 进入/返回/取消/完成/重复进入矩阵。
|
||||
3. 导航门禁完全通过后,运行当前 OpenAPI 上预期失败的 T01 图合同,把 `API-T01-001` 交给后端;只接受用户从更新后的 Apifox 重新导出的 JSON/YAML。
|
||||
1. T01 后端接口门禁已经建立并运行出预期红灯;等待后端按 `API-T01-001` 发布四条 v2 操作并提供同一版本重新导出的 JSON/YAML,不手改当前快照,也不提前实施任务 12。
|
||||
2. TAC 客户端批次已经完成。认证后端仍须关闭 `API-AUTH-TAC-001`—`004`、提供同版本双导出并修复 challenge 500;随后才允许把 `runtimeConfig.mode` 从 `mock` 切为 `remote`,执行正式 H5 CORS、真实票据重放/限流和 MuMu Android 流程。
|
||||
3. 并行推进非交互风控的限时 POC 与文字/中继人工兜底合同;只有 POC、服务端原子消费和 `ANDROID-AUTH-ACCESSIBILITY-RELEASE` 全部通过,才可宣称认证达到上线门槛。
|
||||
4. 新图合同通过后,按规范化与校验、布局、Scene 与空间索引、单 Canvas、页面交互、接口回流的顺序实施 T01。
|
||||
5. T01 完成后,再依次规划短信验证码、跨页面领域数据持久化、全局文字层级与无障碍第二轮,不混合阶段。
|
||||
5. 家谱工作区三人审查和失败合同已经完成;等待后端关闭 `API-GENEALOGY-WORKSPACE-001`—`003` 并提供同版本双导出。门禁通过后再测试先行实现唯一 adapter、G01 `onShow` 列表现场和 G05 `/overview` 读取,不提前固化字段或枚举。
|
||||
6. G03 原子创建三人审查和失败合同已经完成;等待后端关闭 `API-G03-001`—`005`、以同版本双导出交付 bootstrap POST、状态查询、地区选择与统一 accessPreset。通过前不接当前宽松 `createGenealogy`;通过后先实现严格 coordinator、无 PII 恢复 marker、地区选择器、context/G05/G01 闭环,再独立完成 MuMu 表单与无障碍矩阵。
|
||||
7. M06 三人审查和失败合同已经完成;等待后端关闭 `API-M06-001`—`003` 并提供同版本双导出。通过后测试先行实现 list-only adapter、加载/空/失败/重试、动态分类、纯文本手风琴和无障碍语义,不接详情或文章 ID。
|
||||
8. 个人资料读取三人审查和失败合同已经完成;等待后端关闭 `API-PROFILE-READ-001`—`003` 并提供同版本双导出。通过后测试先行实现唯一掩码 adapter 与 M01/M02/M03 局部状态,M05 当前手机号展示随同一只读 owner 原子迁移但不接换绑写接口。
|
||||
9. 通知读取与已读写入已分别完成三人审查和失败合同;等待后端关闭 `API-NOTIFICATION-READ-001`—`003` 与 `API-NOTIFICATION-STATE-001`—`003`。读取通过后先实现无 ID 的内存快照和 M01/G01 未读数;写入另批迁移私有 ID、删除伪本地写并验证并发收敛,不恢复不可信目标 CTA。
|
||||
10. M02 个人资料 PUT 已完成三人审查和失败合同;等待后端关闭 `API-PROFILE-UPDATE-001`—`004`。读取与写入门禁都通过后,按 normalizer、严格 API、M02 状态机和 MuMu 表单矩阵独立实施,不混头像或账号安全。
|
||||
11. M10 服务端退出已完成三人审查和失败合同;等待后端关闭 `API-LOGOUT-001`—`003` 后,再测试先行实现 session epoch、唯一 logoutCoordinator、A01 一次性提示和两设备撤销矩阵。
|
||||
12. M04 登录态改密与四条密码 wire 已完成三人审查和失败合同;等待后端关闭 `API-PASSWORD-001`—`005` 后,才原子迁移共享密码策略、MD5 调用、session marker、M04 状态机和全设备撤销矩阵。
|
||||
13. M05 手机号换绑三人审查和失败合同已经完成;等待后端关闭 `API-PHONE-001`—`005`,且 M04 raw-password 与认证/TAC 前置门禁同时通过后,再原子迁移全活动 OTP、实现专用受保护发码、共享 credential marker 与 M05 状态机。
|
||||
14. 继续审查下一个不依赖现有红灯的业务域;按 G/F/R 逐域推进。随后逐批完成文字层级与无障碍,不混合验证码、账号写入、领域数据或支付。
|
||||
|
||||
Reference in New Issue
Block a user