# N03 SDD W3 整批独立评审报告 ## 评审对象 - 范围:`df7238b..cd9220b` - Wave:W3 - 批次:B3 / T8、T9、T10 - 提交: - `3e353bd feat: expose student skill cloud endpoints` - `7873a42 test: add skill cloud end-to-end regression` - `cd9220b docs: publish skill cloud api contract` - 评审依据:修订后的 `execution-contract.md`、`design.md`、`tasks.md`、三份 spec,以及 `W3-T8*`、`W3-T9.md`。 - 环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`。 ## Verdict **FAIL** W3 的运行时接口、STUDENT 权限、参数校验、DTO 序列化、端到端一致性和打包均通过独立验证;但 T10 发布的 OpenAPI 契约存在两项 Important 问题: 1. 新增 `/skills/match` 的 `operationId=getMatch` 与既有 `/applications/{id}/match` 冲突。 2. 匿名或无效 JWT 的运行时 401 响应为 `code=401` 的裸 JSON,而文档和静态 OpenAPI 声明 `2001` 及统一 `ApiResponse` 语义。 因此 T10 不能判定通过,且本 wave 不允许进入收尾或 TO-11。 ## T8:Controller、权限、参数与 ApiResponse ### 通过项 - 三个路由存在且均为 GET: - `/skills/word-cloud` - `/skills/match` - `/skills/learning-path` - `industryId`、`occupationId` 为必填正整数;`categoryId` 为可选正整数。 - 成功后统一返回 `ResponseEntity>`,并保留 `X-Request-Id`。 - `SecurityConfig` 仅放行已认证请求到 `/skills/**`;Controller 再通过真实 `ProfileService` 按数据库角色校验 STUDENT。 - `SkillCloudAuthorizationIntegrationTest` 使用真实 `UserRepository`、数据库角色集合、JWT 过滤器和 `ProfileService`,没有 mock 核心权限链路。 - 已覆盖 STUDENT=200、COMPANY=403、ADMIN=403、STUDENT+COMPANY=200、COMPANY+ADMIN=403。 - 额外传入 `userId`、`studentId` 不能覆盖 JWT 主体。 - `SkillCloudControllerTest` 共 14 项,授权集成测试共 2 项,独立执行均通过。 - 未发现 B3 修改已有接口语义或引入无关模块变更。提交范围仅涉及新增 Controller、SecurityConfig 一条 GET 规则、技能测试和文档/OpenAPI。 ### 阻断项 #### W3-I1:`operationId=getMatch` 重复 - `docs/openapi-rc.json:1186`:既有 `/applications/{id}/match` GET。 - `docs/openapi-rc.json:6460`:新增 `/skills/match` GET。 - 两处均使用 `operationId: "getMatch"`。 - `SkillCloudController.java:120-121` 的方法名也是 `getMatch`,运行时生成的 operationId 很可能同样冲突;现有测试没有检查全局 operationId 唯一性。 - `docs/verify_openapi.py:146` 反而明确断言新增接口的 operationId 必须是 `getMatch`,把冲突值固化为通过条件。 - OpenAPI 规范要求 operationId 全局唯一;重复值会导致客户端/文档代码生成器出现歧义或相互覆盖。 修复方向:给 `SkillCloudController.getMatch` 显式设置唯一 operationId,例如 `getSkillMatch`,同步静态 OpenAPI 和 verify 脚本,并增加全局 operationId 唯一性断言。 #### W3-I2:401 运行时响应与文档/OpenAPI 不一致 - 运行时匿名请求由 `SecurityConfig.java:50-54` 直接写出 `{"code":401,...}`,且不包含 `requestId`。 - `SkillCloudControllerTest.java:158-164` 明确断言匿名 401 的 `$.code=401`。 - `docs/skill-cloud-api.md:14` 声明使用统一 `ApiResponse`。 - `docs/skill-cloud-api.md:230-240` 声明 401 的典型业务码为 `2001`,示例也返回 `code=2001` 和 `requestId`。 - `docs/openapi-rc.json` 的三个新增 401 响应示例均使用 `code=2001`。 - 因此静态契约与真实运行时对相同的“未登录/无效 JWT”场景给出了不同的业务码和响应语义。 修复方向:二选一并做可证伪测试: - 保持现有全局安全行为,更新接口文档和静态 OpenAPI 为真实 `code=401` 响应;或 - 在授权范围内调整安全入口,使 401 使用统一的 `ApiResponse` 和 `2001`,同时评估对所有既有接口的兼容性。 ## T9:端到端一致性 ### 通过项 `SkillCloudEndToEndIntegrationTest` 共 5 项,使用真实 H2/Flyway、Security、Controller、Service、Repository 和 V36 数据,仅 Mock `RedisTemplate` 以隔离基础设施依赖,独立执行通过。 已验证: - 词云返回完整排序列表,`rank` 连续,`heat DESC`,空热度末位。 - 匹配集合严格等于 `min(10,total)`,`matchedSkills + missingSkills` 等于同一 TOP10,且各自保持顺序。 - 学习路径严格复用 `missingSkills` 顺序,`priority` 从 1 连续递增。 - 少于 10 项时按实际大小计算,`1/3` 返回 33;空范围返回 0 和空列表。 - 重复调用三个接口返回完全相同的数据。 - 合法空分类覆盖 `total=0`、`matchScore=0`、`learningPath.total=0`。 - 成功响应未发现手机号、邮箱、简历文件名/URL 或原始技能 JSON。 - 额外身份查询参数不能覆盖学生 JWT 主体。 ### 非阻断测试缺口 - “无写入”目前只比较 8 张表的行数,只能发现 INSERT/DELETE,不能发现仅 UPDATE 的写操作。 - 隐私断言未显式覆盖 `realName`、`bio`、`skillsJson` 等驼峰或实际值。 - 端到端测试主要证明三个接口复用同一 TOP10,具体名称/别名匹配分支由 T6 测试覆盖,这一点可以接受。 以上均为可加强项,不单独阻断 T9 运行时实现。 ## T10:文档与 OpenAPI ### 通过项 - 静态 OpenAPI path 数从 126 增至 129,三个新路径均存在。 - 三个新路径均声明 `bearerAuth`、200/400/401/403,以及必填/可选参数。 - 新增 Schema 字段与 Java DTO 字段在静态检查中一致。 - `docs/skill-cloud-api.md` 和 `docs/frontend-integration.md` 覆盖 TOP10 截取、匹配公式、排序、空数据、权限、无 AI 和无持久化语义。 - `python docs\verify_openapi.py` 独立执行成功,输出 `paths: 129` 和 `All checks passed`。 - 未发现三个新接口之外的接口语义被改变。 ### 阻断项 见上方 `W3-I1`、`W3-I2`。T10 当前不能判定通过。 ### verify 脚本的不足 - `docs/verify_openapi.py:164` 使用 `expected_fields <= properties`,只验证字段至少存在,不验证字段集合完全相等、类型、nullable、required 或静态契约与运行时的对应关系;新增多余字段仍可通过。 - 脚本没有全局 operationId 唯一性断言,因此 `W3-I1` 未被发现。 - 脚本检查静态 OpenAPI,而 `SkillCloudControllerTest` 检查运行时 `/v3/api-docs`,两者没有做等价性比较;当前静态文件使用 `example`,运行时注解生成使用 `examples`,进一步说明两者可能各自漂移。 - 精确 path 数断言可以用于回归基线,但不能替代 operationId 唯一性、Schema 完整性和错误响应一致性验证。 ## 独立执行结果 ### 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: 71, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS Total time: 59.133 s ``` 分类: - SkillTaxonomyMigrationTest:2 - SkillCloudSeedDataIntegrationTest:8 - SkillTaxonomyRepositoryTest:6 - SkillCloudQueryServiceTest:8 - SkillMatchServiceTest:16 - SkillLearningPathServiceTest:10 - SkillCloudControllerTest:14 - SkillCloudAuthorizationIntegrationTest:2 - SkillCloudEndToEndIntegrationTest:5 ### OpenAPI 校验 ```text python docs\verify_openapi.py paths: 129 All checks passed! ``` 该命令通过,但不覆盖上述 operationId 冲突、401 运行时差异和静态/运行时 Schema 等价性,因此不能抵消 W3-I1、W3-I2。 ### 打包 ```text mvn -DskipTests package BUILD SUCCESS Total time: 2.401 s ``` ### 完整 `mvn test` 独立运行结果: ```text Tests run: 1393, Failures: 2, Errors: 9, Skipped: 0 BUILD FAILURE Total time: 01:58 min ``` Skill Cloud 全部测试类在该套件中均通过。 ## 全量 blocker 归类 以下失败均不在 `df7238b..cd9220b` 修改范围内,不能归因于 W3 引入的行为回归: - `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:固定 Offer 过期时间 `2026-09-05` 已早于当前日期,属于时间依赖测试问题。 - `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:仍 stub 旧服务方法签名。 - `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:仍 stub 旧 Repository 方法,导致 NPE。 - `LocalResumeStorageServiceTest`:8 项均在 Windows JUnit 临时目录清理阶段因 `AccessDeniedException` 报错,属于环境/临时目录问题。 归类结论: - **不是 W3 代码阻断。** - **仍然是项目级验证阻断,应阻止 `closing`/TO-11**,除非先修复,或由项目层面正式记录并批准豁免;合同退出条件不能被视为满足。 - 本次独立验证未运行真实 MySQL 黑盒迁移,仍应保留为发布级验证风险。 ## 最终放行结论 - **是否允许 T8 运行时实现进入后续:允许。** 权限、参数和运行时响应链路通过。 - **是否允许 T9 端到端实现进入后续:允许。** 端到端行为通过,仅有非阻断覆盖缺口。 - **是否允许 W3 整批通过:不允许。** T10 的 OpenAPI/文档契约存在 Important 阻断。 - **是否允许进入收尾/TO-11:不允许。** 需要先修复 W3-I1、W3-I2 并进行聚焦复审;全量 `mvn test` 还需全绿或形成正式豁免记录。