Files
jiapuapp/tests/current-documentation-contract.ps1
T
2026-07-24 07:56:33 +08:00

1042 lines
49 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$currentDocuments = @(
'docs/项目当前总览.md',
'docs/家谱项目全量治理设计.md',
'docs/家谱项目全量治理实施计划.md',
'docs/视觉资产与构建基线.md',
'docs/接口与页面映射总表.md',
'docs/今晚全量联调与明早测试执行计划.md',
'docs/产品参考页面功能映射表.md'
)
$combined = ''
$documentContents = @{}
foreach ($relativePath in $currentDocuments) {
$absolutePath = Join-Path $root $relativePath
if (-not (Test-Path -LiteralPath $absolutePath -PathType Leaf)) {
throw "缺少当前权威文档:$relativePath"
}
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $absolutePath
$documentContents[$relativePath] = $content
$combined += "`n" + $content
}
foreach ($token in @('TO' + 'DO', 'TB' + 'D', 'FIX' + 'ME', 'PLACE' + 'HOLDER')) {
if ($combined -match "(?i)\b$([regex]::Escape($token))\b") {
throw "当前权威文档仍含未完成占位标记:$token"
}
}
# 当前进度只由项目总览表述;其余权威文档不得把已完成的阶段 0 或已收口的书面设计写成进行中。
$designDocument = $documentContents['docs/家谱项目全量治理设计.md']
$planDocument = $documentContents['docs/家谱项目全量治理实施计划.md']
$mappingDocument = $documentContents['docs/接口与页面映射总表.md']
$overviewDocument = $documentContents['docs/项目当前总览.md']
if ($designDocument -match '阶段 0 正在执行') {
throw '治理设计仍把已完成的阶段 0 写成进行中'
}
if ($mappingDocument -match '当前阶段:阶段 0——迁移现行页面与业务事实') {
throw '接口与页面映射总表仍把阶段 0 迁移写成当前任务'
}
if ($mappingDocument -notmatch '家谱工作区、G03 原子创建、M06 帮助、个人资料读写、通知读写、M10 服务端退出、M04 密码凭证与 M05 手机号换绑三人审查及 OpenAPI 红灯已完成' -or
$mappingDocument -notmatch 'T01、TAC、工作区、G03、帮助、profile、通知、logout、password 与 phone-change 后端接口门禁当前红灯') {
throw '接口与页面映射总表没有登记导航、TAC 客户端、后端红灯与 MuMu 边界'
}
if ($designDocument -notmatch '导航任务 1—10 的静态实施和零债务门禁已经完成' -or
$designDocument -notmatch '当前 SFC 分段词法扫描的迁移债务已经清零' -or
$designDocument -notmatch 'MIGRATION-DEBT=0') {
throw '治理设计没有同步导航静态零债务结果'
}
if ($mappingDocument -notmatch '实际 `64/64` 个 Vue 文件') {
throw '接口与页面映射总表没有同步退役通用页面后的响应式覆盖实数'
}
if ($overviewDocument -notmatch '任务 36 普通加入申请' -or
$overviewDocument -notmatch '任务 37 邀请码签发与直接加入' -or
$overviewDocument -notmatch '任务 38 G11 家谱设置版本化写入' -or
$overviewDocument -notmatch '任务 39 G12 字辈集合版本化保存' -or
$overviewDocument -notmatch '任务 40 F01/F03 家族动态读取') {
throw '项目总览没有写明领域上下文与 M07 完成后的当前精确实施入口'
}
if ($overviewDocument -notmatch 'API-T01-001') {
throw '项目总览没有登记 T01 后端接口门禁'
}
if ($overviewDocument -match '当前 MuMu[^\r\n]*在线') {
throw '项目总览不得把易漂移的 MuMu 在线状态写成长期当前事实'
}
if ($overviewDocument -notmatch '阶段 0 验收时的 MuMu 证据') {
throw '项目总览缺少带时间边界的 MuMu 验收证据'
}
$visualDocument = $documentContents['docs/视觉资产与构建基线.md']
if ($visualDocument -notmatch '`static/tac/` 当前共 5 个文件' -or
$visualDocument -notmatch '4 个后端提供的供应商文件' -or
$visualDocument -notmatch '`static/tac/js/jiapu-tac-adapter.js`' -or
$visualDocument -notmatch 'tests/auth-tac-integration-contract.ps1') {
throw '视觉资产基线没有登记 TAC 供应商资产、项目适配器及唯一校验入口'
}
if ($mappingDocument -notmatch 'A01、A04、A05 已接入同一个 `TacVerification`' -or
$mappingDocument -notmatch '不能把本地滑动成功冒充服务端验证' -or
$mappingDocument -notmatch '开发/联调 wire 已按线上' -or
$mappingDocument -notmatch '`validToken` 未进入或被服务端消费') {
throw '接口页面映射没有准确登记 A01 密码联调接线、TAC 客户端状态与生产红灯'
}
if ($mappingDocument -notmatch '任务 5 实施前' -or $mappingDocument -notmatch '这只是历史视觉基线' -or $mappingDocument -notmatch '任务 4—6 的当前代码仍须.*MuMu 流程矩阵') {
throw '接口页面映射把历史 MuMu 证据越界成了当前代码验收'
}
# 导航设计已经三人终审,文档合同必须保护其所有权、根语义、公开方法和阶段边界,不能只检查“有一份计划”。
foreach ($owner in @('utils/navigation-routes.js', 'utils/navigation.js')) {
if ($designDocument -notmatch [regex]::Escape($owner) -or $planDocument -notmatch [regex]::Escape($owner)) {
throw "导航设计或实施计划缺少唯一所有者:$owner"
}
if (-not (Test-Path -LiteralPath (Join-Path $root $owner) -PathType Leaf)) {
throw "导航唯一所有者尚未真实落地:$owner"
}
}
foreach ($semanticMethod in @('openPage', 'goBack', 'returnTo', 'finishPage', 'goRoot')) {
if ($designDocument -notmatch "(?m)^$([regex]::Escape($semanticMethod))\(") {
throw "导航设计缺少公开语义方法:$semanticMethod"
}
}
if ($planDocument -notmatch 'ROOT_ROUTE_KEYS = Object\.freeze\(\["A01", "G01", "F01", "M01"\]\)') {
throw '实施计划没有把四个根语义精确锁定为 A01/G01/F01/M01'
}
if ($planDocument -match 'ROOT_ROUTE_KEYS[^\r\n]*N01') {
throw 'N01 不是根页,不得进入 ROOT_ROUTE_KEYS'
}
if ($designDocument -notmatch 'G01、F01、M01.*自定义 `AppTabbar`' -or $designDocument -notmatch 'A01 是认证根页') {
throw '治理设计没有区分三个业务根页与 A01 认证根页'
}
if ($designDocument -notmatch '字段键精确为 `operation、entityId(可选)、refresh`' -or $designDocument -notmatch '不允许加入 `treeVersion、genealogyId、focusId、payload`') {
throw '一次性导航结果没有保持 operation/entityId/refresh 的唯一形状'
}
if ($designDocument -notmatch 'T03 原生页面实例只保留一个' -or $designDocument -notmatch '轨迹初始化为恰好 `\[initialPersonId\]`') {
throw '治理设计缺少 T03 单实例与初始成员轨迹合同'
}
if ($planDocument -notmatch '导航阶段允许只迁移 T01 的现有导航调用' -or $planDocument -notmatch '导航门禁通过前,不得实施 T01 新图合同') {
throw '导航与 T01 的阶段隔离边界不准确'
}
if ($planDocument -notmatch 'T03_CONTEXT_CONFLICT') {
throw '实施计划缺少 T03 跨家谱上下文冲突规则'
}
if ($planDocument -notmatch 'T03_STACK_CONFLICT') {
throw '实施计划缺少 T03 历史重复实例冲突规则'
}
if ($planDocument -notmatch 'returnTo\(routeKey, targetParams = \{\}\)' -or $planDocument -notmatch 'finishPage\(routeKey, targetParams, result\)') {
throw '实施计划没有锁定普通返回与唯一完成结果入口'
}
if ($planDocument -match 'returnTo\([^\)\r\n]*,\s*result\s*\)') {
throw '实施计划重新引入了 returnTo 携带流程结果的旧入口'
}
if ($planDocument -notmatch 'own enumerable data properties' -or $planDocument -notmatch 'WeakMap') {
throw '实施计划缺少严格数据属性快照或页面实例弱身份令牌合同'
}
$navigationFoundation = [regex]::Match(
$planDocument,
'(?s)### 任务 1:建立路由语义注册表.*?(?=### 任务 3:迁移共享页头、底栏并退役通用母版)'
).Value
if ([string]::IsNullOrWhiteSpace($navigationFoundation) -or $navigationFoundation -match '- \[ \]') {
throw '实施计划没有把已经验证的导航任务 1/2 精确标为完成'
}
if ($planDocument -notmatch '任务 36:建立普通加入申请闭环远端硬门禁' -or
$planDocument -notmatch '任务 37:建立邀请码签发与直接加入远端硬门禁' -or
$planDocument -notmatch '任务 38:建立 G11 家谱设置版本化写入远端硬门禁' -or
$planDocument -notmatch '任务 39:建立 G12 字辈集合版本化保存远端硬门禁' -or
$planDocument -notmatch '任务 40:建立 F01/F03 家族动态读取远端硬门禁') {
throw '实施计划没有登记领域上下文与 M07 完成后的独立实施入口'
}
$taskThree = [regex]::Match(
$planDocument,
'(?s)### 任务 3:迁移共享页头、底栏并退役通用母版.*?(?=### 任务 4:迁移认证导航)'
).Value
if ([string]::IsNullOrWhiteSpace($taskThree) -or $taskThree -match '- \[ \] \*\*步骤 [123]') {
throw '实施计划没有把任务 3 的静态实施步骤精确标为完成'
}
if ($taskThree -notmatch 'MuMu 复核待执行' -or $taskThree -notmatch '39') {
throw '实施计划没有明确任务 3 的 MuMu 边界或剩余迁移债务'
}
$taskFour = [regex]::Match(
$planDocument,
'(?s)### 任务 4:迁移认证导航.*?(?=### 任务 5:迁移 G 系列导航)'
).Value
if ([string]::IsNullOrWhiteSpace($taskFour) -or $taskFour -match '- \[ \] \*\*步骤 [123]') {
throw '实施计划没有把任务 4 的静态实施步骤精确标为完成'
}
if ($taskFour -notmatch '- \[ \] \*\*步骤 4:在 MuMu 复核认证流程' -or $taskFour -notmatch '35') {
throw '实施计划没有明确任务 4 的 MuMu 边界或剩余迁移债务'
}
if ($taskFour -notmatch 'tests/navigation-flow-contract.ps1' -or $taskFour -match 'navigation-auth-flow-contract') {
throw '认证导航没有消费跨批次唯一流程合同'
}
$taskFive = [regex]::Match(
$planDocument,
'(?s)### 任务 5:迁移 G 系列导航.*?(?=### 任务 6:迁移 T 系列并实现 T03 单实例轨迹)'
).Value
if ([string]::IsNullOrWhiteSpace($taskFive) -or $taskFive -match '- \[ \] \*\*步骤 [1234]') {
throw '实施计划没有把任务 5 的静态实施步骤精确标为完成'
}
if ($taskFive -notmatch '- \[ \] \*\*步骤 5:完成 G 系列 MuMu 矩阵' -or $taskFive -notmatch '29 个文件') {
throw '实施计划没有明确任务 5 的 MuMu 边界或剩余迁移债务'
}
if ($taskFive -match 'finishPage\("G05"' -or $taskFive -match 'finishPage\("G09"') {
throw '实施计划重新引入了 G 系列本地预览的伪后端完成结果'
}
if ($taskFive -notmatch '未知值失败关闭' -or $taskFive -notmatch '每批 50 行渲染') {
throw '实施计划没有登记 G11/G12 的最终静态合同'
}
$taskSix = [regex]::Match(
$planDocument,
'(?s)### 任务 6:迁移 T 系列并实现 T03 单实例轨迹.*?(?=### 任务 7:迁移 F 系列导航)'
).Value
if ([string]::IsNullOrWhiteSpace($taskSix) -or $taskSix -match '- \[ \] \*\*步骤 [123]') {
throw '实施计划没有把任务 6 的静态实施步骤精确标为完成'
}
if ($taskSix -notmatch '- \[ \] \*\*步骤 4:完成 T 系列 MuMu 矩阵' -or $taskSix -notmatch '22 个页面/表单组件文件') {
throw '实施计划没有明确任务 6 的 MuMu 边界或剩余迁移债务'
}
if ($taskSix -match 'finishPage\(' -or $taskSix -match 'relative-created|member-updated|relationship-updated') {
throw '实施计划在 T 系列本地预览阶段伪造了服务端写成功结果'
}
foreach ($taskSixContract in @(
'本地预览',
'尚未提交服务器',
'`goBack()`',
'宿主页路由身份',
'页内活动成员',
'listTreeMemberFixtures(genealogyId)',
'findTreeMemberFixture(genealogyId, personId)',
'深拷贝快照',
'跨谱隔离',
'失败关闭',
'tests/tree-member-fixture-runtime-smoke.js',
'PowerShell `122/122`',
'Node 语法 `35/35`',
'纯 Node `7/7`'
)) {
if ($taskSix -notmatch [regex]::Escape($taskSixContract)) {
throw "实施计划缺少任务 6 最终合同:$taskSixContract"
}
}
$taskSeven = [regex]::Match(
$planDocument,
'(?s)### 任务 7:迁移 F 系列导航.*?(?=### 任务 8:迁移 R 系列导航)'
).Value
if ($planDocument -match 'assert\.deepEqual\(consumeNavigationResult\("F04"\),\s*\{\s*operation:\s*"article-created"') {
throw '实施计划仍保留已经废止的 F04 伪写结果示例'
}
if ([string]::IsNullOrWhiteSpace($taskSeven) -or $taskSeven -match '- \[ \] \*\*步骤 [12345]') {
throw '实施计划没有把任务 7 的静态实施步骤精确标为完成'
}
if ($taskSeven -notmatch '- \[ \] \*\*步骤 6:完成 F 系列 MuMu 矩阵' -or $taskSeven -notmatch '13 个页面/表单组件文件') {
throw '实施计划没有明确任务 7 的 MuMu 边界或剩余迁移债务'
}
foreach ($taskSevenContract in @(
'F01—F10',
'跨谱',
'WRITE_UNAVAILABLE',
'feed-created/article-created/article-updated/media-uploaded',
'PowerShell `122/122`',
'Node 语法 `36/36`',
'纯 Node `8/8`',
'MIGRATION-DEBT=13'
)) {
if ($taskSeven -notmatch [regex]::Escape($taskSevenContract)) {
throw "实施计划缺少任务 7 最终合同:$taskSevenContract"
}
}
$taskEight = [regex]::Match(
$planDocument,
'(?s)### 任务 8:迁移 R 系列导航.*?(?=### 任务 9)'
).Value
foreach ($taskEightContract in @(
'R03/R04 归属 `relative-records`',
'`giftId` 全量退役为 `relativeId`',
'`ritualId` 全量退役为 `ceremonyId`',
'R09 必须保持硬关闭',
'resultOperations',
'本地预览,尚未提交服务器',
'只读复合身份 owner',
'MuMu'
)) {
if ($taskEight -notmatch [regex]::Escape($taskEightContract)) {
throw "实施计划缺少任务 8 三方审查结论:$taskEightContract"
}
}
if ([string]::IsNullOrWhiteSpace($taskEight) -or $taskEight -match '- \[ \] \*\*步骤 [123456]') {
throw '实施计划没有把任务 8 的静态实施步骤精确标为完成'
}
if ($taskEight -notmatch '- \[ \] \*\*步骤 7:完成 R 系列 MuMu 矩阵' -or $taskEight -notmatch 'MIGRATION-DEBT=6') {
throw '实施计划没有明确任务 8 的 MuMu 边界或剩余迁移债务'
}
foreach ($taskEightVerification in @('PowerShell `122/122`', 'Node 语法 `37/37`', '纯 Node `9/9`')) {
if ($taskEight -notmatch [regex]::Escape($taskEightVerification)) {
throw "实施计划缺少任务 8 最新验证:$taskEightVerification"
}
}
$taskNine = [regex]::Match(
$planDocument,
'(?s)### 任务 9:迁移 N/M 系列与安全通知目标.*?(?=### 任务 10:关闭导航阶段)'
).Value
if ([string]::IsNullOrWhiteSpace($taskNine) -or $taskNine -match '- \[ \] \*\*步骤 [1234]') {
throw '实施计划没有把任务 9 静态实施步骤精确标为完成'
}
if ($taskNine -notmatch '- \[ \] \*\*步骤 5:完成 N/M 系列 MuMu 矩阵' -or $taskNine -notmatch 'MIGRATION-DEBT=1') {
throw '实施计划没有明确任务 9 的 MuMu 边界或最后一项导航债务'
}
$taskTen = [regex]::Match(
$planDocument,
'(?s)### 任务 10:关闭导航阶段.*?(?=---\s*## 第二阶段:T01 大规模世系树)'
).Value
if ([string]::IsNullOrWhiteSpace($taskTen) -or $taskTen -match '- \[ \] \*\*步骤 [12]') {
throw '实施计划没有把任务 10 的静态门禁步骤精确标为完成'
}
if ($taskTen -notmatch '- \[ \] \*\*步骤 3:执行 NAV-MUMU-01 至 NAV-MUMU-08' -or
$taskTen -notmatch 'MIGRATION-DEBT=0' -or
$taskTen -notmatch 'TreeMemberForm\.vue') {
throw '实施计划没有记录导航零债务、孤儿删除或 MuMu 待验边界'
}
$taskEleven = [regex]::Match(
$planDocument,
'(?s)### 任务 11:向后端提交规范图合同并建立接口门禁.*?(?=### 任务 12:实现新图的规范化与严格校验)'
).Value
if ([string]::IsNullOrWhiteSpace($taskEleven) -or
$taskEleven -match '- \[ \] \*\*步骤 [12]' -or
$taskEleven -notmatch '- \[ \] \*\*步骤 [34]') {
throw '实施计划没有精确登记 T01 门禁已落地、后端双导出仍待提供'
}
foreach ($taskElevenFact in @('LINEAGE-OPENAPI-CONTRACT BLOCKED', 'JSON/YAML 各缺四条操作和三个固定根模型', '平铺 query parameters 无法由 OpenAPI 静态证明')) {
if ($taskEleven -notmatch [regex]::Escape($taskElevenFact)) {
throw "任务 11 缺少门禁分层事实:$taskElevenFact"
}
}
if ($mappingDocument -notmatch '### 2\.12 R 系列线上接口与静态迁移边界') {
throw '接口与页面映射总表缺少 R 系列三方接口审查账本'
}
$taskEighteen = [regex]::Match(
$planDocument,
'(?s)### 任务 18:接入新接口、mock 和跨页变更版本.*?(?=### 任务 19:完成 T01 MuMu、性能与三人终审)'
).Value
foreach ($taskEighteenContract in @(
'pages/tree/t05-edit-member.vue',
'appApi.updateLineagePerson(genealogyId, memberId, payload, treeVersion)',
'T04/T05/T06 完成回流',
'genealogyId/personId/memberId',
'personId=A',
'memberId=B',
'hostPersonId',
'editedMemberId',
'禁止 `memberId || personId` 双读',
'parentParamMap` 仍只负责无历史时构造父页',
'同步收紧 `tests/navigation-flow-contract.ps1`'
)) {
if ($taskEighteen -notmatch [regex]::Escape($taskEighteenContract)) {
throw "任务 18 缺少 T05 双身份或原子合同迁移约束:$taskEighteenContract"
}
}
foreach ($retiredOwner in @('components/ModulePage.vue', 'data/page-catalog.js')) {
if ($combined -match [regex]::Escape($retiredOwner)) {
throw "当前权威文档不得把退役通用页面重新登记为 owner:$retiredOwner"
}
}
if ($combined -notmatch 'tests/retired-module-page-contract.ps1') {
throw '当前权威文档缺少通用母版退役防回归合同'
}
$navigationSource = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'utils/navigation.js')
$actualNavigationExports = @(
[regex]::Matches($navigationSource, '(?m)^export const ([A-Za-z][A-Za-z0-9]*)') |
ForEach-Object { $_.Groups[1].Value } |
Sort-Object
)
$expectedNavigationExports = @(
'buildRouteUrl',
'consumeNavigationResult',
'finishPage',
'goBack',
'goRoot',
'handleBackPress',
'openNoticeTarget',
'openPage',
'resolveBackAction',
'returnTo',
'runBackGuard'
) | Sort-Object
if (($actualNavigationExports -join ',') -ne ($expectedNavigationExports -join ',')) {
throw "导航网关公开导出面漂移:$($actualNavigationExports -join ', ')"
}
if ($navigationSource -match '(?m)^export\s+(const|function)\s+replaceStep\b' -or $designDocument -match '(?m)^replaceStep\(') {
throw '当前没有替换边,不得恢复公开 replaceStep 入口'
}
if ($mappingDocument -match '将在阶段 1.*确定' -or $mappingDocument -match '阶段 1 依赖') {
throw '接口映射总表仍把已收口导航或未来业务错误绑定为阶段 1 待定项'
}
if ($overviewDocument -notmatch '当前物理库存为 `147` 个 PowerShell 合同、`48` 个 Node 文件' -or
$overviewDocument -notmatch 'PowerShell `128/147` 通过' -or
$overviewDocument -notmatch 'Node 语法 `48/48` 通过' -or
$overviewDocument -notmatch '纯 Node 冒烟 `20/20` 通过' -or
$overviewDocument -notmatch 'Vue 脚本模块语法 `64/64` 通过') {
throw '项目总览没有登记当前测试库存与最新全量验证证据'
}
if ($overviewDocument -notmatch '当前物理库存更新为 PowerShell `141`、Node `47`' -or
$overviewDocument -notmatch 'JOIN-APPLICATION-OPENAPI-CONTRACT BLOCKED') {
throw '项目总览没有登记任务 36 新增门禁后的物理库存和红灯'
}
if ($overviewDocument -notmatch '当前物理库存为 PowerShell `142`、Node `47`' -or
$overviewDocument -notmatch '全量 fresh 为 `126/142`' -or
$overviewDocument -notmatch 'INVITE-TICKET-OPENAPI-CONTRACT BLOCKED') {
throw '项目总览没有登记任务 37 新增门禁后的物理库存、fresh 结果和红灯'
}
if ($overviewDocument -notmatch '当前物理库存为 PowerShell `143`、Node `47`' -or
$overviewDocument -notmatch '全量 fresh 为 `126/143`' -or
$overviewDocument -notmatch 'G11-SETTINGS-OPENAPI-CONTRACT BLOCKED' -or
$overviewDocument -notmatch 'Issues: 48') {
throw '项目总览没有登记任务 38 新增门禁后的物理库存、fresh 结果和红灯'
}
if ($overviewDocument -notmatch '当前物理库存为 PowerShell `145`、Node `48`' -or
$overviewDocument -notmatch '全量 fresh 为 `127/145`' -or
$overviewDocument -notmatch 'G12-GENERATION-POEM-OPENAPI-CONTRACT BLOCKED' -or
$overviewDocument -notmatch 'Issues: 72' -or
$overviewDocument -notmatch 'G12-GENERATION-POEM-OPENAPI-ADVERSARIAL-CONTRACT PASS' -or
$overviewDocument -notmatch 'G12-GENERATION-POEM-UNICODE-RUNTIME-SMOKE PASS') {
throw '项目总览没有登记任务 39 门禁、对抗证据和 fresh 库存'
}
if ($overviewDocument -notmatch '当前物理库存为 PowerShell `147`、Node `48`' -or
$overviewDocument -notmatch '全量 fresh 为 `128/147`' -or
$overviewDocument -notmatch 'FAMILY-FEED-READ-OPENAPI-CONTRACT BLOCKED' -or
$overviewDocument -notmatch 'Issues: 85' -or
$overviewDocument -notmatch 'FAMILY-FEED-READ-OPENAPI-ADVERSARIAL-CONTRACT PASS MUTANTS=58') {
throw '项目总览没有登记任务 40 家族动态读取门禁和 fresh 库存'
}
foreach ($completedDomainFact in @(
'appApi.submitFeedback',
'/genealogy/app/feedback',
'WRITE_UNAVAILABLE',
'feedbackContent',
'撤权',
'禁止静默',
'uncertain'
)) {
if ($overviewDocument -notmatch [regex]::Escape($completedDomainFact) -and
$mappingDocument -notmatch [regex]::Escape($completedDomainFact) -and
$planDocument -notmatch [regex]::Escape($completedDomainFact)) {
throw "当前文档没有登记领域上下文或 M07 完成事实:$completedDomainFact"
}
}
foreach ($workspaceGateFact in @(
'API-GENEALOGY-WORKSPACE-001',
'genealogy-workspace-openapi-contract.ps1',
'RListAppGenealogyVo',
'RAppGenealogyVo',
'canView',
'appListMyGenealogies',
'appGetGenealogyOverview',
'GENEALOGY_ID_INVALID',
'NON_DISCLOSING_GENEALOGY_NOT_AVAILABLE',
'OMIT_ONLY_AFTER_CONFIRMED_ACCESS_LOSS',
'旁路详情',
'允许非语义 tracing header',
'禁止 ETag',
'traceparent/tracestate/x-request-id/x-correlation-id',
'内联 Header Object',
'非 null 的 string schema',
'readOnly/writeOnly',
'allOf',
'GENEALOGY-WORKSPACE-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($workspaceGateFact) -and
$mappingDocument -notmatch [regex]::Escape($workspaceGateFact) -and
$planDocument -notmatch [regex]::Escape($workspaceGateFact)) {
throw "当前文档没有登记家谱工作区门禁事实:$workspaceGateFact"
}
}
foreach ($helpGateFact in @(
'API-M06-001',
'help-center-openapi-contract.ps1',
'RListHelpArticleVo',
'HelpArticleVo',
'list-only',
'HELP-CENTER-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($helpGateFact) -and
$mappingDocument -notmatch [regex]::Escape($helpGateFact) -and
$planDocument -notmatch [regex]::Escape($helpGateFact)) {
throw "当前文档没有登记 M06 帮助门禁事实:$helpGateFact"
}
}
foreach ($profileGateFact in @(
'API-PROFILE-READ-001',
'profile-openapi-contract.ps1',
'RAppProfileVo',
'AppProfileVo',
'maskedPhone',
'PROFILE-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($profileGateFact) -and
$mappingDocument -notmatch [regex]::Escape($profileGateFact) -and
$planDocument -notmatch [regex]::Escape($profileGateFact)) {
throw "当前文档没有登记个人资料读取门禁事实:$profileGateFact"
}
}
foreach ($profileUpdateGateFact in @(
'API-PROFILE-UPDATE-001',
'profile-update-openapi-contract.ps1',
'AppProfileMergeUpdateBody',
'profileVersion',
'If-Match',
'PROFILE_VERSION_CHANGED',
'PROFILE-UPDATE-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($profileUpdateGateFact) -and
$mappingDocument -notmatch [regex]::Escape($profileUpdateGateFact) -and
$planDocument -notmatch [regex]::Escape($profileUpdateGateFact)) {
throw "当前文档没有登记个人资料写入门禁事实:$profileUpdateGateFact"
}
}
foreach ($notificationGateFact in @(
'API-NOTIFICATION-READ-001',
'API-NOTIFICATION-STATE-001',
'notification-read-openapi-contract.ps1',
'notification-read-state-openapi-contract.ps1',
'RListNotificationVo',
'RNotificationUnreadCount',
'generationordinal',
'NOTIFICATION-READ-OPENAPI-CONTRACT BLOCKED',
'NOTIFICATION-READ-STATE-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($notificationGateFact) -and
$mappingDocument -notmatch [regex]::Escape($notificationGateFact) -and
$planDocument -notmatch [regex]::Escape($notificationGateFact)) {
throw "当前文档没有登记通知读取或已读写入门禁事实:$notificationGateFact"
}
}
foreach ($logoutGateFact in @(
'API-LOGOUT-001',
'logout-openapi-contract.ps1',
'RLogoutRejected',
'TOKEN_CLIENT_MISMATCH',
'LOGOUT-OPENAPI-CONTRACT BLOCKED',
'logoutCoordinator'
)) {
if ($overviewDocument -notmatch [regex]::Escape($logoutGateFact) -and
$mappingDocument -notmatch [regex]::Escape($logoutGateFact) -and
$planDocument -notmatch [regex]::Escape($logoutGateFact)) {
throw "当前文档没有登记 M10 服务端退出门禁事实:$logoutGateFact"
}
}
foreach ($passwordChangeGateFact in @(
'API-PASSWORD-001',
'API-PASSWORD-005',
'password-change-openapi-contract.ps1',
'CurrentPasswordSecret',
'NewPasswordSecret',
'RPasswordChangeRejected',
'CREDENTIAL_VERSION_CONFLICT',
'credentialChangeInFlight',
'PASSWORD-CHANGE-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($passwordChangeGateFact) -and
$mappingDocument -notmatch [regex]::Escape($passwordChangeGateFact) -and
$planDocument -notmatch [regex]::Escape($passwordChangeGateFact)) {
throw "当前文档没有登记 M04 密码凭证门禁事实:$passwordChangeGateFact"
}
}
foreach ($phoneChangeGateFact in @(
'API-PHONE-001',
'API-PHONE-005',
'phone-change-openapi-contract.ps1',
'PhoneChangeSmsCodeBody',
'NewBoundPhone',
'SmsCodeSecret',
'RPhoneChangeRejected',
'STEP_UP_UNAVAILABLE',
'PHONE-CHANGE-OPENAPI-CONTRACT BLOCKED'
)) {
if ($overviewDocument -notmatch [regex]::Escape($phoneChangeGateFact) -and
$mappingDocument -notmatch [regex]::Escape($phoneChangeGateFact) -and
$planDocument -notmatch [regex]::Escape($phoneChangeGateFact)) {
throw "当前文档没有登记 M05 手机号换绑门禁事实:$phoneChangeGateFact"
}
}
foreach ($g03BootstrapGateFact in @(
'API-G03-001',
'API-G03-005',
'g03-bootstrap-openapi-contract.ps1',
'AppGenealogyBootstrapBody',
'GenealogyBootstrapOperationKey',
'GenealogyAccessPreset',
'GenealogyRegionCode',
'FAILED_NO_COMMIT',
'x-bootstrap-root-editable-fields',
'x-state-transitions',
'x-domain-effects=NONE',
'任意精度',
'G03-BOOTSTRAP-OPENAPI-CONTRACT BLOCKED',
'g03-bootstrap-client-release-gate.ps1',
'G03-BOOTSTRAP-CLIENT-RELEASE BLOCKED',
'openapi-yaml-json-parity-runtime-smoke.js',
'/genealogy/region/search',
'GET 必须纯读',
'fatal/quarantined',
'家谱工作区读取门禁',
'If-Match、版本/CAS',
'receipt→`/mine` cache→context→G05'
)) {
if ($overviewDocument -notmatch [regex]::Escape($g03BootstrapGateFact) -and
$designDocument -notmatch [regex]::Escape($g03BootstrapGateFact) -and
$mappingDocument -notmatch [regex]::Escape($g03BootstrapGateFact) -and
$planDocument -notmatch [regex]::Escape($g03BootstrapGateFact)) {
throw "当前文档没有登记 G03 原子创建门禁事实:$g03BootstrapGateFact"
}
}
foreach ($joinApplicationGateFact in @(
'API-JOIN-001',
'API-JOIN-006',
'join-application-openapi-contract.ps1',
'appSearchPublicGenealogies',
'appCreateGenealogyJoinApplication',
'appGetGenealogyJoinApplicationRequest',
'appListMyGenealogyJoinApplications',
'appWithdrawGenealogyJoinApplication',
'appListPendingGenealogyJoinApplications',
'appReviewGenealogyJoinApplication',
'GenealogyJoinApplicationRequestKey',
'JOIN_APPLICATION_TEXT_V1',
'FAILED_NO_COMMIT',
'WHERE status=PENDING',
'JOIN-APPLICATION-OPENAPI-CONTRACT BLOCKED',
'邀请码成功后直接加入且不生成审核记录',
'G10 手机号',
'消息中心承诺',
'LOCAL_WITHDRAWN',
'数据库观测',
'静态门禁单独转绿不开放页面'
)) {
if ($overviewDocument -notmatch [regex]::Escape($joinApplicationGateFact) -and
$designDocument -notmatch [regex]::Escape($joinApplicationGateFact) -and
$mappingDocument -notmatch [regex]::Escape($joinApplicationGateFact) -and
$planDocument -notmatch [regex]::Escape($joinApplicationGateFact)) {
throw "当前文档没有登记普通加入申请门禁事实:$joinApplicationGateFact"
}
}
foreach ($inviteTicketGateFact in @(
'API-INVITE-001',
'API-INVITE-006',
'invite-ticket-openapi-contract.ps1',
'appListMyGenealogyInviteTickets',
'appIssueGenealogyInviteTicket',
'appRevokeGenealogyInviteTicket',
'appResolveGenealogyInviteTicket',
'appRedeemGenealogyInviteTicket',
'appGetGenealogyInviteRedemptionRequest',
'INVITE-TICKET-OPENAPI-CONTRACT BLOCKED',
'G06',
'不再进入 G08',
'单次、24 小时',
'600 秒',
'HMAC',
'KMS',
'ACTIVE→CONSUMED',
'ACTIVE_PENDING_APPLICATION',
'PENDING/SUCCEEDED/FAILED_NO_COMMIT',
'TalkBack'
)) {
if ($overviewDocument -notmatch [regex]::Escape($inviteTicketGateFact) -and
$designDocument -notmatch [regex]::Escape($inviteTicketGateFact) -and
$mappingDocument -notmatch [regex]::Escape($inviteTicketGateFact) -and
$planDocument -notmatch [regex]::Escape($inviteTicketGateFact)) {
throw "当前文档没有登记邀请码直接加入门禁事实:$inviteTicketGateFact"
}
}
foreach ($settingsGateFact in @(
'API-SETTINGS-001',
'API-SETTINGS-006',
'g11-settings-openapi-contract.ps1',
'appUpdateGenealogySettings',
'GenealogyName',
'GenealogyIntro',
'GenealogySettingsVersion',
'AppGenealogySettingsUpdateBody',
'GENEALOGY_SETTINGS_VERSION_CHANGED',
'GENEALOGY_NOT_READY',
'ACTIVE_PENDING_APPLICATIONS',
'G11-SETTINGS-OPENAPI-CONTRACT BLOCKED',
'Issues: 48',
'dirty-only',
'If-Match',
'ATOMIC_SINGLE_WINNER',
'canonical 设置实际变化',
'内部 LF',
'控制字符',
'exact local',
'单字段',
'nullable',
'真实 JSON 数组',
'允许非语义 tracing header',
'禁止 ETag',
'traceparent/tracestate/x-request-id/x-correlation-id',
'内联 Header Object',
'非 null 的 string schema',
'readOnly/writeOnly',
'根层',
'PUBLIC_APPLY→MEMBER_ONLY',
'fresh overview',
'不创建.*占位 client gate',
'OpenAPI 绿.*不.*直接接线'
)) {
$pattern = if ($settingsGateFact -in @('不创建.*占位 client gate', 'OpenAPI 绿.*不.*直接接线')) {
$settingsGateFact
} else {
[regex]::Escape($settingsGateFact)
}
if ($overviewDocument -notmatch $pattern -and
$designDocument -notmatch $pattern -and
$mappingDocument -notmatch $pattern -and
$planDocument -notmatch $pattern) {
throw "当前文档没有登记 G11 设置门禁事实:$settingsGateFact"
}
}
foreach ($generationPoemGateFact in @(
'API-POEM-001',
'API-POEM-006',
'g12-generation-poem-openapi-contract.ps1',
'g12-generation-poem-openapi-adversarial-contract.ps1',
'g12-generation-poem-unicode-contract-runtime-smoke.js',
'appGetGenerationPoemSet',
'appUpdateGenerationPoemSet',
'/genealogy/app/genealogies/{genealogyId}/generation-poems',
'AppGenerationPoemSetUpdateBody',
'GenerationPoemSetSnapshot',
'GenerationPoemSetVersion',
'disableMissing',
'VALIDATE_FINAL_ACTIVE_CAPACITY',
'ALLOCATE_UNIQUE_NEW_IDS',
'GENERATION_SLOT_CONFLICT',
'POEM_SET_VERSION_CONFLICT',
'FRESH_GET_THREE_WAY_NO_AUTO_PUT',
'CURRENT_EQUALS_TARGET_FIRST_NO_ATTRIBUTION',
'APP_GATEWAY_PREFLIGHT',
'G12-GENERATION-POEM-OPENAPI-CONTRACT BLOCKED',
'Issues: 72',
'G12-GENERATION-POEM-OPENAPI-ADVERSARIAL-CONTRACT PASS',
'G12-GENERATION-POEM-UNICODE-RUNTIME-SMOKE PASS',
'zero issues',
'ACTIVE-only',
'If-Match',
'不引入 preview token',
'不自动 PUT',
'本地预览',
'生产 normalizer/coordinator'
)) {
if ($overviewDocument -notmatch [regex]::Escape($generationPoemGateFact) -and
$designDocument -notmatch [regex]::Escape($generationPoemGateFact) -and
$mappingDocument -notmatch [regex]::Escape($generationPoemGateFact) -and
$planDocument -notmatch [regex]::Escape($generationPoemGateFact)) {
throw "当前文档没有登记 G12 字辈集合门禁事实:$generationPoemGateFact"
}
}
foreach ($familyFeedReadGateFact in @(
'API-FEED-READ-001',
'API-FEED-READ-006',
'family-feed-read-openapi-contract.ps1',
'family-feed-read-openapi-adversarial-contract.ps1',
'appListFamilyFeeds',
'appGetFamilyFeed',
'appListFamilyFeedRootComments',
'/genealogy/app/genealogies/{genealogyId}/feeds',
'/genealogy/app/genealogies/{genealogyId}/feeds/{feedId}',
'/genealogy/app/genealogies/{genealogyId}/feeds/{feedId}/comments',
'FamilyFeedId',
'FamilyFeedCommentId',
'AppFamilyFeedReadItem',
'AppFamilyFeedRootCommentReadItem',
'UPPER_BOUND_KEYSET_LATEST_VISIBLE',
'publishedAt:DESC',
'commentId:ASC_ORDINAL',
'hasMedia',
'FAMILY_FEED_NOT_AVAILABLE',
'FAMILY-FEED-READ-OPENAPI-CONTRACT BLOCKED',
'FAMILY-FEED-READ-OPENAPI-ADVERSARIAL-CONTRACT PASS MUTANTS=58',
'Issues: 85',
'zero issues',
'手机号',
'审核字段',
'删除两个 `/page` GET',
'后端绿前',
'生产 read normalizer/coordinator'
)) {
if ($overviewDocument -notmatch [regex]::Escape($familyFeedReadGateFact) -and
$designDocument -notmatch [regex]::Escape($familyFeedReadGateFact) -and
$mappingDocument -notmatch [regex]::Escape($familyFeedReadGateFact) -and
$planDocument -notmatch [regex]::Escape($familyFeedReadGateFact)) {
throw "当前文档没有登记 F01/F03 家族动态读取门禁事实:$familyFeedReadGateFact"
}
}
foreach ($liveOpenApiFact in @('https://backend-api.ddxcjp.cn/', '3.1.0', '722', '858', '507', '/captcha/challenge', 'validToken')) {
if ($overviewDocument -notmatch [regex]::Escape($liveOpenApiFact) -or $mappingDocument -notmatch [regex]::Escape($liveOpenApiFact)) {
throw "项目总览或接口映射缺少新线上 OpenAPI 事实:$liveOpenApiFact"
}
}
if ($mappingDocument -notmatch '密码登录体尚无票据字段' -or $mappingDocument -notmatch '没有任何 `/genealogy/app/v2/') {
throw '接口映射没有锁定 TAC 密码登录或 T01 v2 的线上硬缺口'
}
foreach ($lineageGateFact in @('LINEAGE-OPENAPI-CONTRACT BLOCKED', 'LineageGraphWindow/LineageOverview/LineageLocator')) {
if ($overviewDocument -notmatch [regex]::Escape($lineageGateFact) -and $mappingDocument -notmatch [regex]::Escape($lineageGateFact) -and $planDocument -notmatch [regex]::Escape($lineageGateFact)) {
throw "当前文档没有登记 T01 门禁事实:$lineageGateFact"
}
}
foreach ($authGateFact in @(
'AUTH-TAC-OPENAPI-CONTRACT BLOCKED',
'ANDROID-AUTH-ACCESSIBILITY-RELEASE BLOCKED',
'API-AUTH-TAC-001',
'API-AUTH-TAC-002',
'API-AUTH-TAC-003',
'API-AUTH-TAC-004',
'runtimeConfig.mode',
'mock',
'15 秒',
'APP_SMS_LOGIN',
'APP_REGISTER',
'APP_FORGOT_PASSWORD'
)) {
if ($overviewDocument -notmatch [regex]::Escape($authGateFact) -and $mappingDocument -notmatch [regex]::Escape($authGateFact) -and $planDocument -notmatch [regex]::Escape($authGateFact)) {
throw "当前文档没有登记认证门禁事实:$authGateFact"
}
}
if ($combined -notmatch '同一验证中心' -or
$combined -notmatch '不得.*无障碍.*绕过' -or
$combined -notmatch '非交互风控' -or
$combined -notmatch '已安全绑定设备' -or
$combined -notmatch '人工.*兜底' -or
$combined -notmatch 'POC') {
throw '当前文档没有锁定 TAC 可访问替代路径的统一票据、安全边界和待验证状态'
}
# A04 线上响应已经迁移,注册成功仍必须建立会话并进入 G01,不能保留旧令牌字段或“注册后再登录”终点。
if ($mappingDocument -notmatch '成功建立登录态并进入 G01' -or $mappingDocument -notmatch 'RAppLoginVo' -or $mappingDocument -notmatch 'AppLoginVo' -or $mappingDocument -notmatch 'access_token') {
throw 'A04 文档没有同时锁定线上登录响应与建立登录态后进入 G01'
}
if ($combined -match 'token/accessToken/tokenValue') { throw '当前权威文档仍把旧登录令牌字段写成目标合同' }
if ($designDocument -notmatch '正式终点不是“注册后再登录”' -or $planDocument -notmatch '不能先回 A01 再让用户重复登录') {
throw '设计或计划没有明确排除注册后再次登录的并行终点'
}
# T01 后端合同、固定 MuMu 配置和人工验收规模已经收口;旧字段只能出现在“必须拒绝”的文字中,不能再出现在目标 JSON 形状。
foreach ($canonicalField in @('schemaVersion', 'treeVersion', 'state', 'entryPersonIds', 'genealogyRootPersonIds', 'redactedGenealogyRootCount', 'rootVisibility', 'pathCompleteness', 'ancestorPathSegments', 'returnedNodeCount', 'partnerRelationship', 'relationshipKind', 'parentRole', 'LINEAGE_QUERY_INVALID', 'LINEAGE_FOCUS_NOT_AVAILABLE', 'RELATIONSHIP_PATCH_EMPTY', 'projectLineageGraph', 'sceneVersion')) {
if ($designDocument -notmatch [regex]::Escape($canonicalField) -and $planDocument -notmatch [regex]::Escape($canonicalField)) {
throw "T01 设计与计划缺少规范字段或阶段:$canonicalField"
}
}
if ($planDocument -match '"partnerIds"\s*:' -or $planDocument -match '"rootPersonIds"\s*:' -or $planDocument -match '"rootReason"\s*:' -or $planDocument -match '"schema"\s*:' -or $planDocument -match '"tree"\s*:') {
throw 'T01 目标 JSON 示例仍在定义已经废弃的旧字段'
}
foreach ($emptyContract in @('"state": "EMPTY"', '"focusPersonId": null', '"entryPersonIds": []', '"generationRange": null', '合法的 `state=EMPTY` 空谱', '`state=POPULATED`')) {
if (-not $planDocument.Contains($emptyContract)) {
throw "T01 计划缺少 EMPTY/POPULATED 判别合同:$emptyContract"
}
}
if ($planDocument -notmatch 'POPULATED 分支要求 nodes 非空、`focusPersonId` 引用其中一个 VISIBLE 节点') {
throw 'T01 POPULATED 仍允许把 REDACTED 节点作为焦点'
}
if ($designDocument -match '无主根时显示合同异常' -or $designDocument -notmatch '只有 `state=POPULATED` 缺少有效焦点或窗口入口时才显示合同异常') {
throw 'T01 仍把合法 EMPTY 误判为无主根合同异常'
}
foreach ($entryInvariant in @('没有 primary 入边的节点集合', '每个入口在当前窗口恰有零条 primary 入边', '每个非入口恰有一条 primary 入边', '`entryPersonIds` 引用不存在人物', '列表内人物 `entryReason=null`', '列表外人物 `entryReason!=null`', '入口仍有 primary 入边')) {
if (-not $planDocument.Contains($entryInvariant) -and -not $designDocument.Contains($entryInvariant)) {
throw "T01 窗口入口与 primary 森林不变量缺失:$entryInvariant"
}
}
if ($planDocument -notmatch 'VisibleLineagePerson.*精确包含' -or $planDocument -notmatch 'RedactedLineagePerson.*只允许' -or $planDocument -notmatch '不得用于 FOCUS、locator、搜索或写接口') {
throw 'T01 人物模型没有按 visibility 严格区分可见字段与匿名字段'
}
if ($planDocument -notmatch 'REDACTED 节点只显示通用隐私说明' -or $planDocument -notmatch 'focusPersonId.*targetPersonId.*稳定 VISIBLE 人物 ID' -or $planDocument -notmatch 'focusable=false、targetPersonId=null') {
throw 'T01 仍可能把 REDACTED opaque ID 用作焦点或聚合跳转目标'
}
if ($planDocument -notmatch 'redacted:\{treeVersion\}:\{token\}' -or $planDocument -notmatch '不得以 `redacted:` 开头的稳定人物 ID') {
throw 'T01 没有提供可执行的稳定人物 ID 与匿名 ID 边界'
}
if ($planDocument -notmatch 'protectedPathPersonIds/protectedPathFamilyUnitIds' -or $planDocument -notmatch '任意时刻每个家庭的真实子女上限始终为 5') {
throw 'T01 投影没有同时保护焦点祖先路径和固定五人渲染上限'
}
if ($planDocument -notmatch 'relationshipKind.*discriminator.*oneOf' -or $planDocument -notmatch 'PARTNER 分支只允许 `relationType/status`') {
throw 'T01 关系修改没有形成按不可变 relationshipKind 区分的唯一请求合同'
}
if ($planDocument -notmatch '至少提交一个可修改字段' -or $planDocument -notmatch '未提交的可修改字段保持原值' -or $planDocument -notmatch '422 RELATIONSHIP_PATCH_EMPTY') {
throw 'T01 关系 PATCH 没有拒绝空更新或没有定义省略字段语义'
}
foreach ($enumContract in @('EMPTY/POPULATED', 'MALE/FEMALE/UNKNOWN', 'VISIBLE/REDACTED', 'GENEALOGY_ROOT/WINDOW_CUT/DISCONNECTED_COMPONENT', 'ANCHOR/PARTNER', 'MARRIAGE/PARTNERSHIP/UNKNOWN', 'ACTIVE/ENDED/UNKNOWN')) {
if ($planDocument -notmatch [regex]::Escape($enumContract)) {
throw "T01 OpenAPI 计划缺少完整枚举:$enumContract"
}
}
if ($planDocument -notmatch '规范化最坏复杂度明确为 `O\(N log N \+ E\)`' -or $planDocument -notmatch '投影只接受已经规范化且校验通过的图' -or $planDocument -notmatch '投影阶段保持 `O\(N\+E\)`') {
throw 'T01 规范化排序与线性投影的复杂度边界不清晰'
}
if (-not $planDocument.Contains('Scene 根对象精确为 `{ sceneVersion, treeVersion, focusPersonId, bounds, items }`') -or $planDocument -notmatch '相同 `sceneVersion` 对应不同序列化 payload.*失败' -or $planDocument -notmatch 'scene\.sceneVersion') {
throw 'T01 Scene 根模型、版本所有权或版本碰撞规则不完整'
}
if ($planDocument -notmatch '瞬时 `selectedId` 不属于 Scene payload' -or $planDocument -notmatch '只改变 `selectedId` 时 Scene payload 与 `scene\.sceneVersion` 不变') {
throw 'T01 瞬时选中态仍可能制造同 sceneVersion 的不同 Scene payload'
}
foreach ($overviewInvariant in @('overview` 只接受必填查询参数 `treeVersion`', 'genealogyPersonCount=0、genealogyRootPersonIds=[]、redactedGenealogyRootCount=0、generationRange=null、buckets=[]', 'genealogyRootPersonIds.length + redactedGenealogyRootCount >= 1', '三类计数总和等于 `genealogyPersonCount`', '隐私根只计入 `redactedGenealogyRootCount`', 'getLineageOverview(genealogyId, treeVersion)')) {
if (-not $planDocument.Contains($overviewInvariant) -and -not $designDocument.Contains($overviewInvariant)) {
throw "T01 全谱概览的版本、空谱、计数或隐私根不变量缺失:$overviewInvariant"
}
}
foreach ($locatorInvariant in @('根可见性与路径完整性独立校验', 'pathCompleteness=REDACTED_GAPS', 'ancestorPathSegments', '可见根+REDACTED_GAPS', '隐私根+REDACTED_GAPS', '可见根路径不从 `rootPersonId` 开始', 'REDACTED 段含人物 ID', 'validateLineageLocator')) {
if (-not $planDocument.Contains($locatorInvariant) -and -not $designDocument.Contains($locatorInvariant)) {
throw "T01 locator 的隐私根或路径完整性分支缺失:$locatorInvariant"
}
}
foreach ($runtimeSemantic in @('tests/lineage-overview-locator-runtime-smoke.js', 'validateLineageOverview', 'bucket 三类计数与 `genealogyPersonCount` 不等', '隐私根 POPULATED', '响应 `treeVersion` 与请求不一致')) {
if (-not $planDocument.Contains($runtimeSemantic)) {
throw "T01 overview 跨字段语义缺少运行时负例门禁:$runtimeSemantic"
}
}
if ($designDocument -match '焦点、版本和展开状态' -or $planDocument -match '共享焦点、版本、展开状态') {
throw 'T01 文档仍残留已删除的页内聚合展开状态'
}
if ($planDocument -notmatch '/genealogy/app/v2/genealogies/' -or $planDocument -match '若旧 `/lineage/tree`') {
throw 'T01 后端路径没有固定为明确 v2 合同'
}
if ($planDocument -notmatch 'emulator-5554 / 720×1280 / 320dpi' -or $designDocument -notmatch '10、50、100、300 代') {
throw 'T01 文档没有锁定当前 MuMu 配置和 10/50/100/300 代人工定位矩阵'
}
$retiredEntrypoints = @(
'design-pipeline/manifests/a01.json',
'design-pipeline/manifests/a01-scroll-skins-v3.json',
'design-pipeline/scripts/build-a01.mjs',
'tests/a01-code-pipeline-contract.ps1',
'docs/design/assets/a01-vnext/source/A01-layered-source-v1.psd',
'build:a01',
'validate:auth-runtime-assets',
'design-pipeline/manifests/g01-background-candidates.json',
'design-pipeline/manifests/module-page-backgrounds.json',
'design-pipeline/scripts/build-module-page-backgrounds.mjs',
'design-pipeline/scripts/build_module_page_backgrounds.py',
'design-pipeline/scripts/build-g01-empty-frame.mjs',
'design-pipeline/tests/test_build_g01_backgrounds.py',
'build:g01-background-candidates',
'build:module-page-backgrounds',
'build:g01-empty-frame'
)
foreach ($entrypoint in $retiredEntrypoints) {
if ($combined.Contains($entrypoint)) {
throw "当前权威文档仍引用已退役入口:$entrypoint"
}
}
foreach ($owner in @(
'design-pipeline/manifests/runtime-assets.json',
'design-pipeline/manifests/application-runtime-assets.json',
'design-pipeline/manifests/shared-scroll-skins-v3.json',
'design-pipeline/manifests/page-backgrounds-v3.json',
'design-pipeline/manifests/g01-state-frame-v3.json',
'tests/runtime-assets-contract.ps1',
'tests/retired-asset-removal-contract.ps1',
'tests/mumu-visual-acceptance-boundary-contract.ps1',
'npm.cmd run validate:runtime-assets',
'npm.cmd run build:page-backgrounds',
'npm.cmd run build:g01-state-frame',
'docs/接口与页面映射总表.md'
)) {
if (-not $combined.Contains($owner)) {
throw "当前权威文档缺少现行所有者:$owner"
}
}
# 当前文档写出的正式 owner 与验证入口必须真实存在;文档不得指向已删除脚本制造“可恢复”的假象。
foreach ($relativePath in @(
'components/GenealogyPageBackground.vue',
'components/ModulePageBackground.vue',
'design-pipeline/manifests/application-runtime-assets.json',
'design-pipeline/manifests/page-backgrounds-v3.json',
'design-pipeline/manifests/g01-state-frame-v3.json',
'design-pipeline/scripts/build-raster-assets.mjs',
'design-pipeline/scripts/build_raster_assets.py',
'tests/runtime-assets-contract.ps1',
'tests/retired-asset-removal-contract.ps1',
'tests/global-heritage-visual-system-contract.ps1'
)) {
if (-not (Test-Path -LiteralPath (Join-Path $root $relativePath) -PathType Leaf)) {
throw "当前正式 owner 或验证入口不存在:$relativePath"
}
}
foreach ($referenceOnly in @(
'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'
)) {
if (-not (Test-Path -LiteralPath (Join-Path $root $referenceOnly) -PathType Leaf)) {
throw "缺少用户批准的视觉锚点:$referenceOnly"
}
if (-not $combined.Contains($referenceOnly)) {
throw "当前文档没有登记 reference-only 视觉锚点:$referenceOnly"
}
}
$pipelinePackage = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'design-pipeline/package.json') | ConvertFrom-Json
if ($pipelinePackage.scripts.'build:page-backgrounds' -ne 'node scripts/build-raster-assets.mjs design-pipeline/manifests/page-backgrounds-v3.json') {
throw 'design-pipeline 缺少长页面背景 schema v3 重建命令'
}
if ($pipelinePackage.scripts.'build:g01-state-frame' -ne 'node scripts/build-raster-assets.mjs design-pipeline/manifests/g01-state-frame-v3.json') {
throw 'design-pipeline 缺少 G01 状态边框 schema v3 重建命令'
}
if ($pipelinePackage.dependencies -and $pipelinePackage.dependencies.sharp) {
throw '通用 Pillow 构建器生效后不得重新引入 Sharp 单用途依赖'
}
# 阶段 0 已结束,文档体系只允许五个长期入口;临时清单、旧规格、旧计划、交接和设计记录不得重新出现。
$temporaryChecklist = Join-Path $root 'docs/项目基线清理清单.md'
if (Test-Path -LiteralPath $temporaryChecklist -PathType Leaf) {
throw '阶段 0 已完成,临时清理清单必须删除'
}
$allowedMarkdown = @($currentDocuments)
$actualMarkdown = @(
Get-ChildItem -LiteralPath (Join-Path $root 'docs') -Recurse -File -Filter '*.md' |
ForEach-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
)
$unexpectedMarkdown = @($actualMarkdown | Where-Object { $_ -notin $allowedMarkdown })
if ($unexpectedMarkdown.Count -gt 0) {
throw "仍存在当前体系之外的旧 Markdown:$($unexpectedMarkdown -join ', ')"
}
foreach ($retiredRootDocument in @('design-qa.md', '记录.md')) {
if (Test-Path -LiteralPath (Join-Path $root $retiredRootDocument) -PathType Leaf) {
throw "仍存在已退役的根目录文档:$retiredRootDocument"
}
}
# 旧 H5/静态设计图不能再次成为长期视觉证据;目录在 fresh checkout 中可以不存在。
$screenDirectory = Join-Path $root 'docs/design/screens'
if (Test-Path -LiteralPath $screenDirectory -PathType Container) {
$retiredScreens = @(Get-ChildItem -LiteralPath $screenDirectory -Recurse -File)
if ($retiredScreens.Count -gt 0) {
throw "仍存在已退役的长期截图:$($retiredScreens.Count) 张"
}
}
foreach ($retiredTest in @(
'tests/a01-design-record-audit.ps1',
'tests/g06-screenshot-consistency.ps1',
'tests/handoff-documentation-contract.ps1',
'tests/repository-handoff-size-contract.ps1'
)) {
if (Test-Path -LiteralPath (Join-Path $root $retiredTest) -PathType Leaf) {
throw "仍存在只保护旧文档或截图的测试:$retiredTest"
}
}
$sddDirectory = Join-Path $root '.superpowers/sdd'
if (Test-Path -LiteralPath $sddDirectory -PathType Container) {
$sddFiles = @(Get-ChildItem -LiteralPath $sddDirectory -Recurse -File)
if ($sddFiles.Count -gt 0) {
throw "仍存在已完成任务的内部 SDD 过程文件:$($sddFiles.Count) 个"
}
}
Write-Output 'CURRENT-DOCUMENTATION-CONTRACT PASS'