Browse Source

docs: record W3 reviews and verification blocker

backup/master-local-20261012
pwklppg 1 month ago
parent
commit
c0f938c7df
  1. 5
      changes/n03-skill-cloud/.spec-superflow.yaml
  2. 9
      changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml
  3. 6
      changes/n03-skill-cloud/.superpowers/sdd/progress.md
  4. 105
      changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8-rereview.md
  5. 87
      changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8.md
  6. 177
      changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T9.md
  7. 168
      changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-final-review.md
  8. 178
      changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-rereview.md
  9. 198
      changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave.md

5
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_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_confirmed: true
dp_4_timestamp: 2026-09-14T08:00:29Z 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_result: adjudicated: approved one focused repair for exact per-skill source and full proxy assertions
dp_5_timestamp: 2026-09-14T09:21:01Z dp_5_timestamp: 2026-09-14T09:21:01Z
execution_paused: false 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 paused_at: 2026-09-14T09:44:45Z
resumed_at: 2026-09-15T01:18:50Z resumed_at: 2026-09-15T01:18:50Z
resume_count: 1 resume_count: 1

9
changes/n03-skill-cloud/.superpowers/sdd/execution-plan.yaml

@ -4,7 +4,7 @@ contract_hash: 819762ae94f2cb3d11820904d013947c367e4bbb32fe8773a3510a253be9ca97
created_at: 2026-09-14T08:00:29Z created_at: 2026-09-14T08:00:29Z
paused: false paused: false
resumed_at: 2026-09-15T01:18:50Z resumed_at: 2026-09-15T01:18:50Z
current: W3 current: verification
waves: waves:
- id: W1 - id: W1
strategy: serial strategy: serial
@ -22,5 +22,8 @@ waves:
strategy: serial strategy: serial
tasks: [T8, T9, T10] tasks: [T8, T9, T10]
depends_on: [W2] depends_on: [W2]
state: eligible state: passed_with_external_blocker
receipt: null 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

6
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: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: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: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.

105
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。

87
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 阻断项,因为它与仓库既有安全配置约定一致。

177
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/发布级验证门禁中完成。
- 本次评审未修改任何实现或测试源码。

168
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 范围内的文件,避免混入无关变更。

178
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 漂移。

198
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<ApiResponse<T>>`,并保留 `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` 还需全绿或形成正式豁免记录。
Loading…
Cancel
Save