# N03 SDD W3 聚焦复审报告 ## 复审对象 - 代码基线:`cd9220b`,修复差异仍位于工作区,未提交。 - 复审范围:W3-I1、W3-I2 两项 Important finding 的聚焦修复。 - 环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`。 - 对照报告:`changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave.md`。 ## Verdict **FAIL** W3-I1 的原始 `/skills/match` operationId 冲突已修复,W3-I2 要求的运行时 401 契约、Markdown 文档和静态 OpenAPI 已对齐;但本次为满足全局 operationId 唯一性而修改了两处既有 `cancel` 契约,新增一项 Important 问题。 因此 W3 仍不能判定通过,不能进入收尾或 TO-11。 ## W3-I1:operationId 冲突 ### 关闭状态 **原始 finding 已关闭。** - `SkillCloudController.java:44` 显式设置 `operationId=getWordCloud`。 - `SkillCloudController.java:92` 显式设置 `operationId=getSkillMatch`。 - `SkillCloudController.java:140` 显式设置 `operationId=getLearningPath`。 - 静态 OpenAPI 三个 Skill Cloud operationId 与上述值完全一致: - `/skills/word-cloud` = `getWordCloud` - `/skills/match` = `getSkillMatch` - `/skills/learning-path` = `getLearningPath` - `docs/verify_openapi.py:165-168` 对三个 operationId 做精确断言。 - `SkillCloudControllerTest.skillOperationIdsAreExactAndGloballyUnique` 通过运行时 `/v3/api-docs` 校验三个接口的 operationId,并确认每个目标 operationId 只对应目标路径。 ### 新增问题 N-I1(Important):修改了既有 OpenAPI 契约 `docs/verify_openapi.py:27-31` 新增了对全文档 operationId 的无条件唯一性断言。为让该断言通过,本次修复还修改了与 N03 无关的两个既有接口: - `POST /interviews/{interviewId}/cancel`:`cancel` → `cancelInterview` - `POST /profile/me/deletion/cancel`:`cancel` → `cancelAccountDeletion` 问题如下: 1. `operationId` 属于 OpenAPI 公共契约,常用于客户端和 SDK 生成。修改既有 operationId 属于兼容性破坏,即使 HTTP 路径和运行时行为不变,也可能导致生成客户端的方法名变化或升级失败。 2. 两个 `cancel` 重复值是本次变更前已经存在的历史问题,不是 N03 引入的问题,也不在 W3-I1 的修复范围内。 3. `InterviewController.java:132-136` 和 `AccountDeletionController.java:112-115` 仍使用方法名 `cancel`,且没有显式 operationId;因此静态 OpenAPI 被改为两个新名称后,静态契约与运行时 SpringDoc 文档发生漂移。 4. `SkillCloudControllerTest.skillOperationIdsAreExactAndGloballyUnique` 的名称虽然包含 “GloballyUnique”,但实际只验证三个 Skill Cloud operationId 各自只出现一次,并没有验证全文档唯一性。真正强制全文档唯一性的是 `verify_openapi.py`,而它迫使本次变更修改历史契约。 **严重级别:Important。** **最小修复建议:** - 将 `docs/openapi-rc.json` 中两个既有接口的 operationId 恢复为 `cancel`,不要在本变更中修改既有接口契约。 - 将 `docs/verify_openapi.py` 的检查改为“新增接口不得产生新冲突”,而不是“修复全部历史冲突”。例如: - 保留已知历史重复集合 `{'cancel'}`; - 断言除该允许集合外没有其他重复 operationId; - 断言 `getWordCloud/getSkillMatch/getLearningPath` 各出现且仅出现一次; - 断言它们不与历史或未来任何非允许 operationId 冲突。 - 如果项目要求彻底消除全局 operationId 重复,应另开变更,同时在两个既有 Controller 上显式设置 operationId,并评估客户端兼容性与迁移方案。 ## W3-I2:401 运行时与文档契约 ### 关闭状态 **本次要求范围内的 finding 已关闭。** - 运行时实现仍由全局 Security 入口返回: - `code=401` - `message="not authenticated"` - `timestamp=""` - 不包含 `requestId` - 不包含 `data` - `SkillCloudControllerTest.anonymousRequestUsesSecurityEntryPointContract` 对三个接口执行上述字段级断言,并额外断言响应字段集合恰好为 `code/message/timestamp`。 - `SkillCloudAuthorizationIntegrationTest.anonymousAndInvalidJwtUseSecurityEntryPointContract` 同时覆盖匿名请求和 `Bearer invalid-token`。 - `docs/skill-cloud-api.md` 已明确区分 `401` 裸 JSON 与统一 `ApiResponse`,示例与运行时一致。 - 静态 OpenAPI 三个 Skill Cloud `401` 响应均引用 `UnauthorizedResponse`,示例均为: - `code=401` - `message="not authenticated"` - `timestamp=""` - 无 `requestId`、无 `data` - `docs/verify_openapi.py:173-185` 精确校验上述静态 401 契约。 ### 非阻断观察(Minor) `SkillCloudController.java:66、114、162` 的 401 注解仍使用 `@Schema(implementation = ApiResponse.class)`。因此运行时 `/v3/api-docs` 的 401 schema 仍可能展示通用 `ApiResponse`,而静态 OpenAPI 已改为 `UnauthorizedResponse`。本次要求明确检查的是 Markdown 文档和静态 OpenAPI,且该问题不阻碍运行时行为或本 finding 的既定修复;如运行时 API 文档也被视为发布契约,应另行聚焦修正控制器注解并补充运行时 OpenAPI 断言。 ## 独立验证结果 ### T2–T9 目标测试 命令: ```powershell $env:JAVA_HOME='D:\java\jdk' & 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest,SkillLearningPathServiceTest,SkillCloudControllerTest,SkillCloudAuthorizationIntegrationTest,SkillCloudEndToEndIntegrationTest' test ``` 结果: ```text Tests run: 73, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS Total time: 55.399 s ``` 分类统计: - SkillTaxonomyMigrationTest:2 - SkillCloudSeedDataIntegrationTest:8 - SkillTaxonomyRepositoryTest:6 - SkillCloudQueryServiceTest:8 - SkillMatchServiceTest:16 - SkillLearningPathServiceTest:10 - SkillCloudControllerTest:15 - SkillCloudAuthorizationIntegrationTest:3 - SkillCloudEndToEndIntegrationTest:5 ### OpenAPI 静态校验 命令: ```powershell python docs\verify_openapi.py ``` 结果: ```text paths: 129 All checks passed! ``` 结论:静态校验通过,但该通过依赖于 N-I1 所描述的既有 operationId 修改,不能据此判定 W3 通过。 ### 打包 命令: ```powershell $env:JAVA_HOME='D:\java\jdk' & 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -DskipTests package ``` 结果: ```text BUILD SUCCESS ``` ### 完整 `mvn test` 项目级回归 命令: ```powershell $env:JAVA_HOME='D:\java\jdk' & 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' test ``` 结果: ```text Tests run: 1395, Failures: 2, Errors: 9, Skipped: 0 BUILD FAILURE ``` 所有 Skill Cloud 目标测试在全量套件中通过;失败仍集中在既有或环境相关测试: - `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:时间依赖失败。 - `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:旧 Mock 签名导致 500。 - `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:旧 Repository stub 导致 NPE。 - `LocalResumeStorageServiceTest`:Windows JUnit 临时目录 `AccessDeniedException`,8 个错误。 因此不能声称完整 `mvn test` 通过。上述 2 failures + 9 errors 仍是项目级 blocker,未形成正式豁免前不能关闭变更或进入 TO-11。 ## 剩余阻断项 1. **N-I1(Important)**:恢复既有 `cancel` operationId,并调整校验脚本,使唯一性检查只拦截新增冲突,不以修改历史契约为代价。 2. **项目级验证 blocker**:完整 `mvn test` 仍有 2 failures + 9 errors,需修复或正式豁免。 3. **Minor**:运行时 `/v3/api-docs` 的 401 schema 仍可能需要从通用 `ApiResponse` 切换为 `UnauthorizedResponse`,以避免静态/运行时 OpenAPI 漂移。