diff --git a/changes/n03-skill-cloud/.spec-superflow.yaml b/changes/n03-skill-cloud/.spec-superflow.yaml index c52748b..0ef0613 100644 --- a/changes/n03-skill-cloud/.spec-superflow.yaml +++ b/changes/n03-skill-cloud/.spec-superflow.yaml @@ -27,10 +27,13 @@ plan_revision: 1 dp_4_result: approved: SDD selected; W1 serial T1-T3, W2 serial T4-T7 depends W1, W3 serial T8-T10 depends W2 dp_4_confirmed: true dp_4_timestamp: 2026-09-14T08:00:29Z -batches_completed: 2 +batches_completed: 3 dp_5_result: adjudicated: approved one focused repair for exact per-skill source and full proxy assertions dp_5_timestamp: 2026-09-14T09:21:01Z execution_paused: false +verification_blocked: true +verification_blocker: full mvn test has 2 failures + 9 errors in unrelated CareerProgress/JobReview/LocalResume tests +closure_requires: user-approved waiver or authorized repair paused_at: 2026-09-14T09:44:45Z resumed_at: 2026-09-15T01:18:50Z resume_count: 1 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml b/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml index cf332c5..50dfe1d 100644 --- a/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml +++ b/changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml @@ -4,7 +4,7 @@ contract_hash: 819762ae94f2cb3d11820904d013947c367e4bbb32fe8773a3510a253be9ca97 created_at: 2026-09-14T08:00:29Z paused: false resumed_at: 2026-09-15T01:18:50Z -current: W3 +current: verification waves: - id: W1 strategy: serial @@ -22,5 +22,8 @@ waves: strategy: serial tasks: [T8, T9, T10] depends_on: [W2] - state: eligible - receipt: null \ No newline at end of file + state: passed_with_external_blocker + receipt: pass (W3 final review; 73 targeted tests, OpenAPI 129 paths) +verification_blocked: true +verification_blocker: full mvn test has 2 failures + 9 errors in unrelated CareerProgress/JobReview/LocalResume tests +closure_requires: user-approved waiver or authorized repair of unrelated failures \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/progress.md b/changes/n03-skill-cloud/.superpowers/sdd/progress.md index 678d8cf..0804835 100644 --- a/changes/n03-skill-cloud/.superpowers/sdd/progress.md +++ b/changes/n03-skill-cloud/.superpowers/sdd/progress.md @@ -21,3 +21,9 @@ - 2026-09-15T02:14:37Z Task T6: complete (commit 6eb30e7; review W2-T6-rereview PASS; 40 tests). T7 eligible. - 2026-09-15T02:23:08Z Task T7: failed attempt 1/3 - review W2-T7 FAIL; missing-skill sequence test not falsifiable because names/IDs were already ascending. - 2026-09-15T02:36:53Z W2 wave review PASS; 50 tests; W3/T8 eligible. Minor items carried to T8/T9. +- 2026-09-15T02:47:27Z Task T8: failed attempt 1/3 - review W3-T8 FAIL; OpenAPI examples inconsistent and role tests mocked ProfileService. +- 2026-09-15T02:59:54Z Task T8: complete (commit 3e353bd; review W3-T8-rereview PASS; 66 tests). T9 eligible. +- 2026-09-15T03:17:22Z Task T9: complete (commit 7873a42; review W3-T9 PASS; 71 targeted tests). External full-suite blocker recorded: 2 failures + 9 errors in unrelated CareerProgress/JobReview/LocalResume tests; no T9 impact. T10 docs may proceed, final verification remains blocked. +- 2026-09-15T03:29:18Z W3 wave review FAIL: duplicate operationId getMatch and 401 response contract mismatch; full-suite external blocker remains open. +- 2026-09-15T03:40:12Z W3 rereview FAIL: N-I1 existing cancel operationIds changed by global uniqueness check; restore legacy and scope check to new operations. +- 2026-09-15T03:46:09Z W3 final review PASS for T8-T10; 73 targeted tests and OpenAPI/package checks pass. W3 code complete. Final closure blocked by unrelated full-suite failures: 2 failures + 9 errors. Waiver or out-of-scope repair authorization required. diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8-rereview.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8-rereview.md new file mode 100644 index 0000000..00a4409 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8-rereview.md @@ -0,0 +1,105 @@ +# W3-T8 独立复审报告 + +## 复审结论 + +- Verdict:**PASS** +- 复审范围: + - `mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillCloudController.java` + - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudControllerTest.java` + - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudAuthorizationIntegrationTest.java` + - 对照前次报告 `changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8.md` +- 复审方式:静态核对实现、测试断言、DTO、JWT/角色链路与测试清理;独立运行指定 8 个测试类。未修改任何实现或测试文件。 + +## 原 Important finding 关闭状态 + +### T8-I1:CLOSED + +前次问题为三个 OpenAPI 200 示例的 `total`、计数与数组长度矛盾,且测试未读取示例内容。当前修复已关闭该问题: + +- `SkillCloudController.java:56`:词云示例改为 `total=1` 且 `items` 为 1 项。 +- `SkillCloudController.java:103`:匹配示例改为 `matchScore=50`、`matchedCount=1`、`totalCount=2`,命中 1 项、缺失 1 项。 +- `SkillCloudController.java:150`:学习路径示例改为 `total=1` 且 `items` 为 1 项。 +- `SkillCloudControllerTest.java:267-307`:测试读取 `/v3/api-docs` 中的 `responses.200.content.application/json.examples.success.value`,使用 Jackson 解析字符串示例或对象示例。 +- `SkillCloudControllerTest.java:278-280`:词云 data 字段集合精确匹配,`total == items.size()`,技能项字段集合与 `SkillCloudItem` 一致。 +- `SkillCloudControllerTest.java:282-292`:匹配字段集合精确匹配,`matchedSkills.size == matchedCount`、`missingSkills.size == totalCount - matchedCount`,并按计数校验 `matchScore`。 +- `SkillCloudControllerTest.java:294-299`:学习路径字段集合精确匹配,`total == items.size()`,路径项字段集合与 `SkillLearningPathItem` 一致。 +- 对照 DTO: + - `SkillCloudResponse.java`:`industry`、`occupation`、`category`、`total`、`items`。 + - `SkillCloudItem.java`:`rank`、`skillId`、`skillCode`、`nameZh`、`nameEn`、`sourceType`、`sourceCode`、`categoryId`、`categoryName`、`heat`、`heatSourceCode`、`heatElementId`、`heatProxy`。 + - `SkillMatchResponse.java`:`matchScore`、`matchedCount`、`totalCount`、`matchedSkills`、`missingSkills`。 + - `SkillLearningPathResponse.java`:`total`、`items`。 + - `SkillLearningPathItem.java`:`priority`、`skillId`、`skillName`、`categoryId`、`categoryName`、`suggestion`。 + +可证伪性:单独篡改 `matchScore` 会在 `SkillCloudControllerTest.java:289-290` 失败;篡改 `total` 会在 `:279`、`:288` 或 `:297` 失败;缩短或扩长数组会因元素数量不一致失败;直接删除示例或改坏 JSON 会在 `:302-306` 失败。 + +### T8-I2:CLOSED + +前次问题为角色测试通过 Mock `ProfileService` 按 `userId` 决定结果,未证明真实数据库角色集合。当前修复已关闭该问题: + +- `SkillCloudAuthorizationIntegrationTest.java:42-46`:使用 `@SpringBootTest`、MockMvc、真实 `integration-test` profile 和独立 H2 数据库。 +- `:55-74`:测试注入真实 `UserRepository`、真实 `JwtTokenProvider`;只 Mock 三个技能服务及 Redis,不 Mock `ProfileService`。 +- `:95-108`:真实保存 `STUDENT`、`COMPANY`、`ADMIN`、`STUDENT+COMPANY`、`COMPANY+ADMIN` 五种角色集合,并在三个端点上分别断言 200、403、403、200、403。 +- `SkillCloudController.java:185-198`:身份只取认证主体 `userId`,随后调用真实 `profileService.assertStudentRole(userId)`。 +- `ProfileService.java:50-57`:真实读取 `UserRepository` 并按 `User.roles` 中是否包含 `STUDENT` 判断。 +- `JwtAuthenticationFilter.java:34-40`:JWT 仅用于解析 subject 并建立认证主体;测试 token 即使统一携带 `roles=STUDENT` claim,生产链路仍按数据库角色拒绝 COMPANY/ADMIN,因此断言真实可证。 + +可证伪性:若实现改为信任 JWT 的 `roles` claim,COMPANY/ADMIN 场景会因测试 token 的 `STUDENT` claim 返回 200 而失败;若改回 Mock 按 userId 放行,数据库角色集合测试在本类中无法满足,且真实 `ProfileService` 未替换。 + +## 额外身份查询参数 + +- `SkillCloudAuthorizationIntegrationTest.java:112-130`:对三个端点同时传入公司用户的 `userId` 和 `studentId`,但使用学生 JWT;全部返回 200,并验证 `SkillMatchService` 与 `SkillLearningPathService` 收到 JWT 学生 ID,`SkillCloudQueryService` 只收到筛选参数。 +- `SkillCloudController.java:73-83`、`:120-130`、`:167-177`:三个接口均未声明 `userId` 或 `studentId` 请求参数,身份只来自 `resolveAuthenticatedStudent()`。 +- 结论:额外查询参数不能覆盖 JWT 主体。 + +## 端点、参数与安全回归 + +- `SkillCloudController.java:73`、`:120`、`:167`:三个 GET 路由保持为 `/skills/word-cloud`、`/skills/match`、`/skills/learning-path`。 +- 三个方法均要求正整数 `industryId`、`occupationId`,可选正整数 `categoryId`;`@Validated` 与 `@Positive` 生效。 +- `SecurityConfig.java:77-78`:`GET /skills/**` 要求 authenticated,匿名请求无法进入 Controller。 +- `SkillCloudControllerTest.java:156-165`:三个端点匿名请求均断言 401。 +- `SkillCloudControllerTest.java:168-206`:覆盖必填参数缺失、非正整数和可选参数非法返回 400。 +- `SkillCloudControllerTest.java:110-154`:三个 STUDENT 成功路径均返回统一 `ApiResponse` 和对应 Service DTO 载荷。 +- 未发现 T8 修改导致三个端点、参数校验或 SecurityConfig 行为回归。 + +## 测试清理与范围 + +- `SkillCloudControllerTest.java:50-51` 与 `SkillCloudAuthorizationIntegrationTest.java:45-46` 使用不同的 H2 数据库名,避免类别间共享数据。 +- `SkillCloudControllerTest.java:105-108`、`SkillCloudAuthorizationIntegrationTest.java:89-92` 均在每个测试后执行 `userRepository.deleteAll()`。 +- 指定 8 个测试类完成后全部通过,未观察到外键残留或后续测试污染。 +- 当前工作区中未出现 `docs/skill-cloud-api.md`,未修改 `docs/openapi-rc.json`、`docs/frontend-integration.md`,未实现 T9/T10。 + +## 独立测试结果 + +环境:JDK 17.0.16,Maven 3.9.7。 + +执行命令: + +```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' test +``` + +结果: + +```text +Tests run: 66, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Total time: 53.008 s +``` + +分类结果: + +- `SkillTaxonomyMigrationTest`:2 +- `SkillCloudSeedDataIntegrationTest`:8 +- `SkillTaxonomyRepositoryTest`:6 +- `SkillCloudQueryServiceTest`:8 +- `SkillMatchServiceTest`:16 +- `SkillLearningPathServiceTest`:10 +- `SkillCloudControllerTest`:14 +- `SkillCloudAuthorizationIntegrationTest`:2 + +## 剩余阻断项 + +无 Critical、Important 阻断项。T8-I1、T8-I2 均已关闭,T8 可进入后续流程。 + +非阻断备注:200 响应目前通过示例和测试断言锁定字段及计数,未显式绑定具体响应 schema;本轮要求已满足,不阻断 T8。 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8.md new file mode 100644 index 0000000..1df8931 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8.md @@ -0,0 +1,87 @@ +# W3-T8 独立评审报告 + +## 评审结论 + +- Verdict:**FAIL** +- 评审对象: + - `mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillCloudController.java` + - `mydreamjob-server/src/main/java/com/mydreamjob/config/SecurityConfig.java` + - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudControllerTest.java` +- 依据:修订后的 `execution-contract.md`、`design.md`、`tasks.md`,三份 spec,以及现有 JWT/Security/Controller 约定。 +- 评审方式:静态核对实现与契约,独立运行指定回归,检查 Git 工作区范围;未修改实现文件。 + +## 独立测试结果 + +执行命令: + +```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' test +``` + +结果: + +```text +Tests run: 69, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +``` + +其中 `SkillCloudControllerTest` 为 19 项通过。`git diff --check HEAD` 对 T8 三个文件无空白错误。`docs/openapi-rc.json`、`docs/frontend-integration.md` 无改动,`docs/skill-cloud-api.md` 不存在;未发现 T9/T10 实现。 + +## 阻断项 + +### T8-I1(Important):OpenAPI 成功示例与 DTO/运行时契约矛盾 + +文件与行号: + +- `SkillCloudController.java:56`:词云示例中 `data.total=15`,但 `items` 只有 1 项。 +- `SkillCloudController.java:103`:匹配示例中 `matchedCount=8`、`totalCount=10`,但同时返回 `matchedSkills=[]`、`missingSkills=[]`。 +- `SkillCloudController.java:150`:学习路径示例中 `data.total=2`,但 `items` 只有 1 项。 +- `SkillCloudControllerTest.java:247-269`:OpenAPI 测试只检查路径存在、`bearerAuth`、状态码和参数必填性,没有校验示例内容或 200 响应 schema,因此上述矛盾不会使测试失败。 +- `changes/n03-skill-cloud/execution-contract.md:120`:明确要求“OpenAPI 注解、示例和实际响应必须一致”。 + +影响:客户端联调、接口文档和运行时契约可能按错误示例理解 TOP10、命中/缺失列表与总数关系。应修正三个示例,使计数、数组长度和 DTO 字段一致,并在 T8 的 OpenAPI 测试中锁定关键示例字段;同时建议为 200 响应声明对应 `SkillCloudResponse`、`SkillMatchResponse`、`SkillLearningPathResponse` schema,而非只有示例。 + +### T8-I2(Important):角色测试通过 Mock 按 userId 放行,未证明真实 STUDENT 校验和多角色集合 + +文件与行号: + +- `SkillCloudControllerTest.java:93-97`:`ProfileService` 被 `@MockBean` 替换,并按 `100L/200L/300L` 直接设置成功或抛 `FORBIDDEN`。 +- `SkillCloudControllerTest.java:161-171`:测试传入 `COMPANY`/`ADMIN` 角色声明,但实际 403 取决于 mock 对 `200L` 的硬编码行为,而非数据库角色集合。 +- `SkillCloudControllerTest.java:296-301`:JWT 的 `roles` claim 只作为测试辅助值传入。 +- `SkillCloudController.java:185-198`:Controller 确实从 `SecurityContext` 取身份并调用 `profileService.assertStudentRole(userId)`。 +- `ProfileService.java:50-57`:生产实现从数据库读取用户,并使用 `anyMatch(STUDENT)`,因此单角色和包含 STUDENT 的多角色在代码层面是正确的。 +- 现有 `ProfileControllerTest.java:167-185` 覆盖了数据库单角色 COMPANY/ADMIN 的 403,但没有覆盖 STUDENT+COMPANY 或 STUDENT+ADMIN 多角色集合,也没有覆盖本组 `/skills/**` 端点的真实角色链路。 + +影响:T8 的 Controller 测试无法证明 `COMPANY/ADMIN` 是被真实服务端角色检查拒绝,也无法证明多角色用户中只要包含 STUDENT 就能通过;它可能在 `ProfileService.assertStudentRole` 被替换或角色逻辑回归时仍通过。应增加使用真实 `ProfileService`、真实数据库用户的集成场景,至少覆盖: + +- COMPANY、ADMIN:预期 403。 +- STUDENT+COMPANY:预期允许。 +- 无 STUDENT 的多角色集合:预期 403。 + +## 非阻断项 + +### T8-M1:身份注入测试只覆盖 `/skills/match` + +文件与行号:`SkillCloudControllerTest.java:214-225`。 + +当前实现三个端点都调用同一个 `resolveAuthenticatedStudent()`,代码层面正确;但测试只验证 `match` 端点会忽略 `userId`/`studentId`,没有对词云和学习路径端点做同样断言。建议参数化覆盖三个路径,并 `verify` 服务收到的是 JWT 中的 `100L`。 + +### T8-M2:隐私断言只匹配 `skills_json` + +文件与行号:`SkillCloudControllerTest.java:227-244`。 + +测试没有覆盖驼峰形式 `skillsJson` 或其他可能的新增个人技能字段。当前返回 DTO 为受控记录类型,因此非阻断;建议后续端到端测试使用真实服务响应,并对 DTO schema/序列化字段做更严格断言。 + +## 检查结论 + +- 三个路径和参数语义:**通过**。`industryId`、`occupationId` 必填,`categoryId` 可选;实测缺参/0/负数返回 400。 +- 身份来源:**通过**。Controller 只从 `SecurityContext` 取主体,不绑定 `userId`/`studentId`。 +- 权限行为:**实现通过,测试证明不完整**。匿名 401、COMPANY/ADMIN 403 的 MockMvc 场景通过;但角色拒绝依赖 mock,多角色未覆盖。 +- `ApiResponse` 与成功 DTO:**通过**。成功响应统一包装,字段与 Service DTO 一致。 +- OpenAPI:**不通过**。注解具备 Tag、Operation、SecurityRequirement、参数说明和 200/400/401/403,但成功示例与 DTO/运行时数据矛盾,且 200 响应缺少明确 schema。 +- 改动范围:**通过**。未修改 `docs/openapi-rc.json`,未实现 T9/T10。 + +## 未决问题 + +- 当前安全层的 401/403 JSON 由既有 `SecurityConfig` 直接写出,不是 `ApiResponse` 实体序列化结果;是否在本变更中统一该历史约定,需由项目维护者决定。本次未将其列为 T8 阻断项,因为它与仓库既有安全配置约定一致。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T9.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T9.md new file mode 100644 index 0000000..c9bea9a --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T9.md @@ -0,0 +1,177 @@ +# W3-T9 独立评审报告 + +## 评审结论 + +- Verdict:**PASS** +- T9 是否允许提交:**允许** +- 是否允许继续 T10:**允许继续实现与文档阶段,但不得在本次全量回归未全绿前判定 T10/B3 验证通过** +- 评审范围: + - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudEndToEndIntegrationTest.java` + - `target/skill-cloud-t9-red.log` + - `target/skill-cloud-t9-green.log` + - `target/skill-cloud-t2-t9-regression.log` + - `target/skill-cloud-t9-full-regression.log` + - `target/skill-cloud-t9-unrelated-failures-isolated.log` + - 修订后的 contract、design、tasks、三份 spec,以及 T8 Controller +- 评审方式:静态核对测试与生产链路,使用 JDK 17 / Maven 3.9.7 独立执行指定 T2–T9 测试组、四个失败测试类及完整 `mvn test`。未修改实现、测试或其他源码文件。 + +## T9 独立结论 + +### 真实链路 + +T9 使用 `@SpringBootTest`、`@AutoConfigureMockMvc`、`@ActiveProfiles("integration-test")` 和独立 H2 数据库,实际经过 Controller、Security、ProfileService、Skill 查询/匹配/学习路径 Service、Repository、Flyway 与 V36 种子数据。仅 `RedisTemplate` 被 Mock,核心业务链路没有被替换。 + +可证伪证据: + +- `SkillCloudEndToEndIntegrationTest.java:48-55`:真实 Spring 上下文、MockMvc、H2 和生产数据库 URL/Profile。 +- `:71-90`:注入真实 MockMvc、JdbcTemplate、JwtTokenProvider、Repository;唯一 `@MockBean` 为 RedisTemplate。 +- `:100-134`:真实 STUDENT JWT 和真实 StudentProfile 调用三个端点,断言后端职位返回 16 项真实 V36 技能、rank 连续、heat 降序。 +- `:269-304`:匹配 union、命中/缺失顺序、学习路径 priority/顺序、建议非空均有断言。 +- 无 Flyway 或种子数据时,依赖的固定节点、16 项技能和 V36 来源字段无法成立;测试已独立通过,证明不是纯 Mock 测试。 + +### 词云 TOP10 与匹配一致性 + +T9 从真实词云结果直接取 `allSkills.subList(0, min(10, allSkills.size()))` 作为 TOP10,再验证: + +- `totalCount` 等于实际 TOP10 大小。 +- `matchedCount` 等于 matchedSkills 大小。 +- matchedSkills 与 missingSkills 的并集严格等于词云 TOP10。 +- matchedSkills 保持词云顺序,missingSkills 保持词云顺序。 +- `matchScore` 等于 `round(matchedCount * 100 / expectedTop10.size())`。 +- 3 项技能场景验证 `1/3 = 33`,并验证空热度末位。 + +该设计可证伪“匹配服务另起排序”“TOP10 与实际列表不一致”“missingSkills 丢项或重排”等缺陷。 + +### 学习路径 + +T9 将学习路径逐项与 `match.missingSkills` 对比: + +- 总数为缺失技能数。 +- 每项 `priority` 从 1 连续递增。 +- `skillId`、`skillName` 与缺失技能逐项一致。 +- `suggestion` 非空。 +- 已命中的技能不会出现在学习路径中。 + +### 确定性、空场景、身份与隐私 + +- `:123-133`:三个端点重复调用后 `data` 完全相等,并断言 8 张相关表的行数不变。 +- `:179-225`:事务内插入空分类,验证词云、匹配、学习路径分别返回空列表和 0 值。 +- `:227-240`:学生 JWT 额外传入 COMPANY 的 `userId`、`studentId`,三个端点结果均不被覆盖。 +- `:242-267`:成功响应不包含 `skills_json`、手机号、邮箱、简历 URL、简历原始文件名和 JWT;错误响应不包含异常类、Spring 内部路径、SQL 或堆栈。 + +### T9 findings + +- **Critical**:无。 +- **Important**:无。 +- **Minor T9-M1**:无写入断言以 8 张表行数前后不变为依据,可发现 INSERT/DELETE,但不能发现仅 UPDATE 同一行而不改变行数的情况。当前三个 Service 均为只读事务且测试通过,因此不阻断;后续可增加事务/数据库快照或只读约束断言。 +- **Minor T9-M2**:隐私测试未单独断言 `realName`、`bio` 的哨兵值,也未显式检查 `skillsJson` 驼峰名;已检查字段覆盖敏感号码、邮箱、简历标识、原始 `skills_json` 和 Token,风险为受控 DTO 下的低风险,不阻断。 +- **Minor T9-M3**:T9 的匹配一致性验证以结果中的 matchedIds 反推期望,因此主要证明“匹配结果与 TOP10 一致”,不独立证明具体学生技能命中哪一项标准技能;别名和匹配判定本身已由 T6 测试覆盖,不阻断。 + +### 测试隔离与假绿风险 + +- T9 类级 `@Transactional`,创建的 Student、StudentProfile 和测试用空分类在方法结束后回滚。 +- T9 使用独立 H2 URL,不与其他测试类共享数据库名。 +- 空分类使用固定测试 ID 并依赖事务回滚,没有观察到跨方法或跨类污染。 +- `@BeforeEach` 仅重置 Redis Mock,不依赖测试执行顺序。 +- 未发现共享 H2、事务传播或顺序依赖导致的假绿。 + +## 独立测试结果 + +### 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: 01:08 min +``` + +分类统计: + +- SkillTaxonomyMigrationTest:2 +- SkillCloudSeedDataIntegrationTest:8 +- SkillTaxonomyRepositoryTest:6 +- SkillCloudQueryServiceTest:8 +- SkillMatchServiceTest:16 +- SkillLearningPathServiceTest:10 +- SkillCloudControllerTest:14 +- SkillCloudAuthorizationIntegrationTest:2 +- SkillCloudEndToEndIntegrationTest:5 + +合计:71。 + +### 全量测试 + +执行命令: + +```powershell +$env:JAVA_HOME='D:\java\jdk' +& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' test +``` + +独立结果: + +```text +Tests run: 1393, Failures: 2, Errors: 9, Skipped: 0 +BUILD FAILURE +Total time: 01:57 min +``` + +失败集合与实现者日志一致。 + +## 全量失败归因 + +### T9 阻断 + +无。全量失败集合中没有任何 Skill Cloud 测试或依赖 T9 测试数据的用例。 + +### 项目既有失败 + +#### 1. CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries + +- 位置:`CareerProgressServiceTest.java:213`。 +- 现象:期望 pendingOfferResponseCount=1,实际=0。 +- 根因:测试构造的 Offer 固定过期时间 `2026-09-05T23:59:59`;当前日期为 `2026-09-15`,生产 `CareerProgressService.buildTodo()` 使用 `expireAt.isAfter(now)` 过滤,因此该 Offer 已过期。属于时间依赖的既有测试问题。 +- 与 N03 关系:该测试和生产类自 `bf75fc0` 后未在 `d00a630..HEAD` 中修改,且在不运行任何 Skill Cloud 测试时独立复现。 + +#### 2. JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores + +- 位置:`JobReviewDimensionControllerTest.java:160-162`。 +- 现象:HTTP 200 期望,实际 500;`JobReviewController.list` 收到 null page。 +- 根因:Controller 已调用 7 参数 `listReviews(jobId, ratingType, createdFrom, createdTo, unreplied, page, size)`,测试仍 stub 旧的 3 参数 `listReviews(JOB_ID, 0, 20)`,Mockito 未命中导致 null。 +- 与 N03 关系:相关 Controller、Service 和测试均未在 `d00a630..HEAD` 中修改,且独立复现。 + +#### 3. JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1 + +- 位置:`JobReviewDimensionServiceTest.java:206-210`。 +- 现象:`reviewRepository.findPublicReviews(...)` 返回 null,触发 `JobReviewService.java:169` NPE。 +- 根因:测试仍 stub 旧方法 `findByJobIdAndStatusAndModerationStatusInOrderByCreatedAtDesc(...)`,与当前实现调用的 `findPublicReviews(...)` 不一致。 +- 与 N03 关系:相关 Repository、Service 和测试均未受 N03 变更修改,且独立复现。 + +### 环境阻塞 + +#### 4. LocalResumeStorageServiceTest + +- 现象:8 项测试在 JUnit extension context 清理阶段报 `AccessDeniedException`,路径为 Windows 临时目录下的 `junit-*`。 +- 根因:`WindowsPath.toRealPath()` 在测试 `@TempDir` 清理时访问被拒,测试业务逻辑本身未报断言失败。 +- 与 N03 关系:属于 Windows 临时目录权限/沙箱环境问题;相关类和测试自初始提交后未受 N03 修改,且独立复现。 + +## 门禁决定 + +- **T9**:PASS。T9 测试具备真实持久化链路、TOP10 一致性、学习路径顺序、确定性、空场景、身份和隐私断言,并已通过 T2–T9 指定测试组。 +- **提交 T9**:允许仅提交 `SkillCloudEndToEndIntegrationTest.java` 及本评审报告;不得把当前工作区其他无关改动混入该提交。 +- **继续 T10**:允许进入 T10 的实现与文档工作,但必须先明确处理全量 `mvn test` 的 11 个非 T9 失败。可以将其登记为项目既有/环境阻断并单独修复,或在项目层面正式接受并记录豁免;在此之前不得宣称 TO-11、B3 或发布级验证通过,也不得执行最终收尾。 +- **剩余阻断**:仅存在项目级全量回归阻断,不存在 T9 阻断。 + +## 证据限制 + +- 本次验证使用 H2 `MODE=MySQL` 和 Flyway,未执行真实 MySQL 黑盒迁移;MySQL 验证仍应在 T10/发布级验证门禁中完成。 +- 本次评审未修改任何实现或测试源码。 \ No newline at end of file diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-final-review.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-final-review.md new file mode 100644 index 0000000..7b7c20f --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-final-review.md @@ -0,0 +1,168 @@ +# N03 SDD W3 最终独立复审报告 + +## 复审对象 + +- 变更:`n03-skill-cloud` +- 基线:`df7238b` +- 复审焦点:`W3-N-I1` 修复及其对 `W3-I1`、`W3-I2` 的回归影响。 +- 工作区对象:`docs/openapi-rc.json`、`docs/verify_openapi.py`,并核对 `SkillCloudController` 和权限测试中的既有修复。 +- 环境:JDK 17(`D:\java\jdk`)、Maven 3.9.7(`D:\maven\apache-maven-3.9.7\bin\mvn.cmd`)。 +- 前轮报告:`changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-rereview.md`。 + +## Verdict + +**PASS** + +`W3-N-I1` 已关闭。两个既有 `cancel` operationId 已恢复,静态 OpenAPI 不再受无关历史契约变更影响;Skill Cloud 三个 operationId 的冲突检查仍有效。`W3-I1`、`W3-I2` 保持关闭。W3 代码可以进入收尾和提交阶段。 + +该 PASS 仅针对 W3/N-I1 代码与契约修复,不代表完整项目回归通过。完整 `mvn test` 仍有 2 failures + 9 errors,变更最终关闭或 TO-11 前仍需修复或正式豁免。 + +## N-I1 关闭证据 + +### 既有 cancel 契约已恢复 + +当前 `docs/openapi-rc.json`: + +- `POST /interviews/{interviewId}/cancel`:`operationId = cancel` +- `POST /profile/me/deletion/cancel`:`operationId = cancel` + +对三个 Git 状态进行了全量 operationId 对比: + +```text +df7238b -> HEAD +新增: +/skills/word-cloud getWordCloud +/skills/match getMatch +/skills/learning-path getLearningPath + +HEAD -> 当前工作区 +仅: +/skills/match getMatch -> getSkillMatch + +df7238b -> 当前工作区 +仅三个新增 Skill Cloud operationId,其中 /skills/match 为 getSkillMatch +``` + +因此: + +- 两个历史 `cancel` 值已恢复,没有再次被改名为 `cancelInterview` 或 `cancelAccountDeletion`。 +- 除三个 Skill Cloud operationId 外,没有其他 operationId 在基线 `df7238b` 到当前工作区之间发生变化。 +- `docs/verify_openapi.py` 已移除全文档唯一性要求,只对三个 Skill Cloud operationId 逐个执行全文档“恰好出现于对应路径”的断言。 +- 历史重复值 `cancel` 不再阻断校验,但 Skill Cloud 的新 operationId 仍不能与其他 operation 冲突。 + +结论:**N-I1 已关闭。** + +## W3-I1 保持关闭 + +当前契约与运行时测试保持一致: + +- `/skills/word-cloud` -> `getWordCloud` +- `/skills/match` -> `getSkillMatch` +- `/skills/learning-path` -> `getLearningPath` + +`SkillCloudControllerTest` 在运行时 `/v3/api-docs` 上校验三个 operationId 的精确值及其唯一归属;`docs/verify_openapi.py` 对静态 OpenAPI 执行同等约束。前轮的 `getMatch` 重复冲突没有回退。 + +结论:**W3-I1 保持关闭。** + +## W3-I2 保持关闭 + +401 契约未回退: + +- 运行时匿名请求和非法 JWT 均返回 `code=401`、`message=not authenticated`、`timestamp=""`。 +- 响应不包含 `requestId` 或 `data`。 +- `SkillCloudAuthorizationIntegrationTest` 同时覆盖匿名和 `Bearer invalid-token`。 +- 静态 OpenAPI 中三个 Skill Cloud 接口的 401 均引用 `UnauthorizedResponse`,示例与运行时一致。 +- `docs/verify_openapi.py` 继续校验 401 schema、精确响应字段和示例内容。 + +结论:**W3-I2 保持关闭。** + +## 独立验证结果 + +### 静态 OpenAPI 校验 + +命令: + +```powershell +python docs\verify_openapi.py +``` + +结果: + +```text +paths: 129 +All checks passed! +``` + +通过。 + +### 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: 54.628 s +``` + +通过。 + +### 打包 + +命令: + +```powershell +$env:JAVA_HOME='D:\java\jdk' +& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -DskipTests package +``` + +结果: + +```text +BUILD SUCCESS +Total time: 2.387 s +``` + +通过。 + +## 项目级 blocker + +完整回归命令: + +```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 +Total time: 01:50 min +``` + +失败项: + +- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:期望 1,实际 0,时间依赖问题。 +- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:期望 200,实际 500,旧 Mock 签名问题。 +- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:返回结果 null 导致 NPE,旧 Repository stub 问题。 +- `LocalResumeStorageServiceTest`:8 项错误,Windows JUnit 临时目录清理阶段异常。 + +Skill Cloud 目标测试在完整套件中均通过,但完整 `mvn test` 未全绿。上述 2 failures + 9 errors 仍是项目级 blocker;在修复或正式豁免前,不得声称完整回归通过,也不得进入最终变更关闭或 TO-11。 + +## 收尾结论 + +- W3-N-I1:**关闭** +- W3-I1:**保持关闭** +- W3-I2:**保持关闭** +- 是否允许 W3 代码收尾:**允许** +- 是否允许最终变更关闭:**不允许**,完整 `mvn test` 仍有 2 failures + 9 errors,需修复或正式豁免。 +- 提交范围要求:仓库中存在与本复审无关的暂存、修改和未跟踪文件;W3 收尾提交时必须只包含 N03 范围内的文件,避免混入无关变更。 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-rereview.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-rereview.md new file mode 100644 index 0000000..9f72513 --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-rereview.md @@ -0,0 +1,178 @@ +# 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 漂移。 diff --git a/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave.md b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave.md new file mode 100644 index 0000000..f00cc0d --- /dev/null +++ b/changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave.md @@ -0,0 +1,198 @@ +# 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` 还需全绿或形成正式豁免记录。