Compare commits
59 Commits
master
...
backup/mas
| Author | SHA1 | Date |
|---|---|---|
|
|
c197273f8f | 3 weeks ago |
|
|
0b4f394c4d | 1 month ago |
|
|
1611fcbf31 | 1 month ago |
|
|
0a2baf4804 | 1 month ago |
|
|
ed212c8e6b | 1 month ago |
|
|
29286b3a2c | 1 month ago |
|
|
afdd0049c6 | 1 month ago |
|
|
c93b0d61fe | 1 month ago |
|
|
27dbc095e0 | 1 month ago |
|
|
4b42a624d7 | 1 month ago |
|
|
2d315f90e4 | 1 month ago |
|
|
79f0646da8 | 1 month ago |
|
|
837c0632c6 | 1 month ago |
|
|
36c0214723 | 1 month ago |
|
|
aa472cf90b | 1 month ago |
|
|
f8b3c4040c | 1 month ago |
|
|
23e0fb96a5 | 1 month ago |
|
|
50384e9e85 | 1 month ago |
|
|
6e7d537f8a | 1 month ago |
|
|
310d931207 | 1 month ago |
|
|
aad0ba6a29 | 1 month ago |
|
|
73afd87e50 | 1 month ago |
|
|
97b3b16261 | 1 month ago |
|
|
ca85ab5410 | 1 month ago |
|
|
a5730a7acb | 1 month ago |
|
|
7370e510a8 | 1 month ago |
|
|
037ddc79d7 | 1 month ago |
|
|
3e7c6e7f63 | 1 month ago |
|
|
ef4a9b875b | 1 month ago |
|
|
7fe96cc7a8 | 1 month ago |
|
|
a2c66b3f7d | 1 month ago |
|
|
2db244e6b2 | 1 month ago |
|
|
67d83863f1 | 1 month ago |
|
|
e4a1482d64 | 1 month ago |
|
|
c0f938c7df | 1 month ago |
|
|
411def40c3 | 1 month ago |
|
|
cd9220b506 | 1 month ago |
|
|
7873a42dc8 | 1 month ago |
|
|
3e353bd755 | 1 month ago |
|
|
df7238b436 | 1 month ago |
|
|
f296df453c | 1 month ago |
|
|
fca1160c8c | 1 month ago |
|
|
6eb30e7fd0 | 1 month ago |
|
|
1666162252 | 1 month ago |
|
|
e3eecfe1a6 | 1 month ago |
|
|
52751e5b2b | 1 month ago |
|
|
0c778249cc | 1 month ago |
|
|
b960036aa3 | 1 month ago |
|
|
5fa759c1d8 | 1 month ago |
|
|
4904455877 | 1 month ago |
|
|
1efee77b8c | 1 month ago |
|
|
93477bd0bf | 1 month ago |
|
|
b45538d76f | 1 month ago |
|
|
d00a63069d | 2 months ago |
|
|
0f194cebef | 2 months ago |
|
|
2955305f02 | 2 months ago |
|
|
72465f8c3b | 2 months ago |
|
|
21b853b759 | 2 months ago |
|
|
6a1e870390 | 2 months ago |
262 changed files with 26572 additions and 15 deletions
@ -0,0 +1,47 @@ |
|||||
|
version: 1 |
||||
|
state: executing |
||||
|
workflow: full |
||||
|
change_name: n03-skill-cloud |
||||
|
artifact_language: zh-CN |
||||
|
dp_0_confirmed: true |
||||
|
dp_0_result: confirmed |
||||
|
dp_0_timestamp: 2026-09-14T07:30:33Z |
||||
|
dp_0_decisions: | |
||||
|
变更名称:n03-skill-cloud;一句话意图:基于 ESCO + O*NET 开源词库提供行业技能词云、个人 TOP10 技能匹配度和学习路径接口。 |
||||
|
工作流:full;选择原因:新增模块、Flyway 数据结构、三个 API、权限与测试,属于跨模块行为变更。 |
||||
|
约束:仅登录 STUDENT 可访问;行业和职位必填、技能分类默认全部;热度使用 O*NET Importance;无权重技能保留且排后;不调用 AI;不接课程平台;首版仅覆盖互联网、金融、教育及代表性职位;词库通过 Flyway 落库。 |
||||
|
相关优化:保持聚焦,不纳入平台岗位热度聚合、后台词库维护、学习进度记录或前端开发。 |
||||
|
沟通方式:按决策点审查,规划产物一次生成后统一确认。 |
||||
|
artifact_language=zh-CN |
||||
|
dp_1_result: confirmed |
||||
|
dp_1_timestamp: 2026-09-14T07:30:33Z |
||||
|
dp_2_result: approved: proposal, three delta specs, design, and tasks validated |
||||
|
dp_2_confirmed: true |
||||
|
dp_2_timestamp: 2026-09-14T07:51:20Z |
||||
|
contract_hash: 819762ae94f2cb3d11820904d013947c367e4bbb32fe8773a3510a253be9ca97 |
||||
|
dp_3_result: approved: execution contract B1-B3; revision 2 allows ESCO primary plus O*NET software_skills supplemental source with machine-traceable importance provenance |
||||
|
dp_3_confirmed: true |
||||
|
dp_3_timestamp: 2026-09-14T07:56:28Z |
||||
|
execution_mode: sdd |
||||
|
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: 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: false |
||||
|
verification_outcome: conditional-pass-with-formal-waiver |
||||
|
dp_6_result: pass: 74 Skill Cloud tests, OpenAPI 129 paths, package and startup fix verified; 3 unrelated pre-existing full-suite failures formally waived by user |
||||
|
dp_6_timestamp: 2026-09-15T08:40:05Z |
||||
|
test_result: pass: scoped verification with formal waiver |
||||
|
paused_at: 2026-09-14T09:44:45Z |
||||
|
resumed_at: 2026-09-15T01:18:50Z |
||||
|
resume_count: 1 |
||||
|
paused_reason: user requested immediate pause; W2 may only resume at T4 on manual request |
||||
|
resume_from: T4 |
||||
|
resume_mode: manual |
||||
|
artifacts: |
||||
|
order: [proposal, specs, design, tasks] |
||||
|
skip: [] |
||||
@ -0,0 +1,29 @@ |
|||||
|
plan_revision: 1 |
||||
|
selected_mode: sdd |
||||
|
contract_hash: 819762ae94f2cb3d11820904d013947c367e4bbb32fe8773a3510a253be9ca97 |
||||
|
created_at: 2026-09-14T08:00:29Z |
||||
|
paused: false |
||||
|
resumed_at: 2026-09-15T01:18:50Z |
||||
|
current: verification |
||||
|
waves: |
||||
|
- id: W1 |
||||
|
strategy: serial |
||||
|
tasks: [T1, T2, T3] |
||||
|
depends_on: [] |
||||
|
state: passed |
||||
|
receipt: pass (T1, T2, T3 final review) |
||||
|
- id: W2 |
||||
|
strategy: serial |
||||
|
tasks: [T4, T5, T6, T7] |
||||
|
depends_on: [W1] |
||||
|
state: passed |
||||
|
receipt: pass (W2-wave; 50 tests) |
||||
|
- id: W3 |
||||
|
strategy: serial |
||||
|
tasks: [T8, T9, T10] |
||||
|
depends_on: [W2] |
||||
|
state: passed_with_external_blocker |
||||
|
receipt: pass (W3 final review; 73 targeted tests, OpenAPI 129 paths) |
||||
|
verification_blocked: false |
||||
|
verification_outcome: conditional-pass-with-formal-waiver |
||||
|
closure_requires: DP-7 confirmation pending |
||||
@ -0,0 +1,32 @@ |
|||||
|
# N03 SDD Progress Ledger |
||||
|
|
||||
|
- 2026-09-14T08:00:29Z Plan revision 1 created for mode SDD. |
||||
|
- W1 eligible: T1 → T2 → T3 (serial, no prerequisite). |
||||
|
- W2 blocked on W1: T4 → T5 → T6 → T7. |
||||
|
- W3 blocked on W2: T8 → T9 → T10.- 2026-09-14T08:05:32Z Task T1: complete (commit b45538d, review W1-T1 PASS, 10 representative positions fixed). |
||||
|
- 2026-09-14T08:19:38Z Task T2: complete (review W1-T2 PASS, RED->GREEN, 2 tests pass). |
||||
|
- 2026-09-14T08:28:32Z Task T2: complete (commit 93477bd, review W1-T2 PASS, migration tests 2/2). |
||||
|
- 2026-09-14T08:42:56Z Task T3: failed attempt 1/3 - critical source-model conflict; review W1-T3 FAIL. Hybrid O*NET software skills not authorized by current contract. |
||||
|
- 2026-09-14T09:07:23Z Task T3: failed attempt 2/3 - review W1-T3-rereview FAIL; data correct but tests only sampled provenance. |
||||
|
- 2026-09-14T09:17:50Z Task T3: failed attempt 3/3 - review W1-T3-rereview-2 FAIL; source data correct but test falsifiability still incomplete. Automatic dispatch stopped; human adjudication required. |
||||
|
- 2026-09-14T09:21:01Z DP-5 adjudication: continue with one focused test-falsifiability repair. |
||||
|
- 2026-09-14T09:38:16Z Task T3: complete after adjudicated repair (final review PASS; 10 tests green; 221 exact mappings; W1 complete). |
||||
|
- 2026-09-14T09:44:38Z User paused execution immediately. Active T4 agent was terminated before completion; partial uncommitted T4 files were removed. W1 remains passed. Resume manually from T4 only; do not start T5-T10 earlier. |
||||
|
- 2026-09-15T01:18:50Z Manual resume accepted. W1 remains passed; W2/T4 is eligible. T5-T10 remain blocked until predecessor reviews pass. |
||||
|
- 2026-09-15T01:29:44Z Task T4: failed attempt 1/3 - review W2-T4 FAIL; category join lacks same-occupation guard and test cannot falsify cross-position mismatch. |
||||
|
- 2026-09-15T01:37:17Z Task T4: complete (commits 0c77824, 52751e5; review W2-T4-rereview PASS; 16 tests). T5 eligible. |
||||
|
- 2026-09-15T01:48:56Z Task T5: failed attempt 1/3 - review W2-T5 FAIL; SkillCloudItem omitted sourceType/sourceCode. |
||||
|
- 2026-09-15T01:54:38Z Task T5: complete (commits e3eecfe, 1666162; review W2-T5-rereview PASS; 24 tests). T6 eligible. |
||||
|
- 2026-09-15T02:08:59Z Task T6: failed attempt 1/3 - review W2-T6 FAIL; tests do not isolate nameZh and alias matching branches. |
||||
|
- 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. |
||||
|
- 2026-09-15T05:48:03Z Runtime startup bug: Hibernate schema validation failed on occupation_skills.importance_is_proxy (found BIT, expected TINYINT). Root cause: forced @JdbcTypeCode(SqlTypes.TINYINT) unlike existing Boolean mappings. Fix dispatched with mapping regression test. |
||||
|
- 2026-09-15T06:03:54Z Runtime bug fix attempt 2: first fix removed forced TINYINT but H2 still stores TINYINT and expects BOOLEAN; 34 tests failed. New hypothesis: add V37 to normalize column to BOOLEAN (MySQL alias TINYINT(1)) and remove test-only preferred type override. |
||||
|
- 2026-09-15T06:20:09Z Runtime startup fix PASS: V37 normalizes importance_is_proxy to cross-dialect BOOLEAN, entity uses default Boolean mapping; 74 targeted tests and package pass without extra JVM properties. |
||||
@ -0,0 +1,54 @@ |
|||||
|
# W1-T1 独立评审报告 |
||||
|
|
||||
|
## 结论 |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
T1 文档交付满足已批准执行契约中 B1 对来源、覆盖边界、映射责任、导入规则和迁移安全的要求,未发现会阻断 T2 开始的 Critical 或 Important finding。 |
||||
|
|
||||
|
## 评审范围与证据 |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\data\source-manifest.md` |
||||
|
- `D:\1\zhi\zhilai\docs\skill-taxonomy-sources.md` |
||||
|
- 对照依据:`execution-contract.md`、`tasks.md`、`design.md` 及三份 spec。 |
||||
|
|
||||
|
独立检查结果: |
||||
|
|
||||
|
1. 两份文件均存在且非空:manifest 为 8332 bytes/77 行,docs 为 8485 bytes/79 行。移除各自标题及 `docs` 的引言段后,事实正文逐行完全一致。 |
||||
|
2. 固定来源完整:两份文件均明确 ESCO dataset v1.2.1(Last update 10/12/2025,Release v5.7.0)、O*NET 31.0 Database、对应许可、官方归属 URL 和 ESCO 指定署名语句。证据:manifest 第 7-10、47-52 行;docs 第 9-12、49-54 行。 |
||||
|
3. 运行时离线约束明确:禁止运行时访问 ESCO、O*NET 或在线词库,首版数据随 Flyway 进入本地数据库。证据:manifest 第 10、43 行;docs 第 12、45 行。 |
||||
|
4. 项目映射责任明确:项目维护中文名称、别名、细粒度技能分类及行业/职位映射;上游来源 ID 原样保存、不得重编号或伪造。证据:manifest 第 36-40、58-63 行;docs 第 38-42、60-65 行。 |
||||
|
5. T2/T3 分工明确:T2 只建七张表、外键、唯一约束和索引;T3 才导入三类行业、代表性职位、分类、技能、别名及 O*NET Importance。证据:manifest 第 34 行;docs 第 36 行。 |
||||
|
6. 中文名与缺权规则明确:入选技能必须有非空 `name_zh`;无 O*NET Importance 的技能必须保留并写 `importance=null`,查询映射为 `heat=null` 且排在有权重技能之后。证据:manifest 第 37-38 行;docs 第 39-40 行。 |
||||
|
7. 迁移安全规则明确:计划使用 V35/V36;若编号被占用必须回退刷新;不得改写 V1-V34 或任何已执行迁移。当前迁移目录仍以 V34 为最新,V35/V36 均不存在,因此该规则与当前仓库状态一致。证据:manifest 第 42 行;docs 第 44 行。 |
||||
|
8. 覆盖矩阵覆盖互联网、金融、教育三类行业,共 10 个实际列出的代表性职位,计数为互联网 4、金融 3、教育 3,每个职位均给出首批技能分类范围。证据:manifest 第 14-30 行;docs 第 16-32 行。 |
||||
|
9. 未发现 `TBD`、`TODO`、“稍后确定”、“待定”或 placeholder;未发现与已批准 spec、design 或 execution contract 的行为冲突。 |
||||
|
10. 未发现 T1 越界实现:`com.mydreamjob.skill` 主代码和测试目录均不存在,V35/V36 迁移也不存在,符合 T1 只交付来源文档的范围。 |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
1. **派发文字中的“11 个职位”与实际枚举的 10 个职位不一致。** |
||||
|
已批准 execution-contract、tasks.md、design.md 和 spec 均只规定“互联网、金融、教育及代表性职位”,没有规定硬性 11 个;本轮派发提示中的枚举实际为互联网 4、金融 3、教育 3,共 10 个。两份交付物覆盖全部实际枚举职位,并明确说明未自行新增未批准职位。该差异不影响 T1 验收,也不阻断 T2;建议后续派发统一按 10 个职位表述,除非人工补充第 11 个已批准职位名称。 |
||||
|
|
||||
|
2. **T1 固化的是技能分类范围和技能级来源规则,而不是逐个技能实例的 ESCO/O*NET 来源 ID 与中文标准名清单。** |
||||
|
这与 T1 的“可审查数据边界”目标一致,不构成当前阻断;具体技能级来源 ID、中文标准名及 Importance 映射仍须在 T3 种子数据中逐项落实,并由迁移测试验证。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
- 无阻断 T2 的未决问题。 |
||||
|
- T3 必须履行文档已固定的技能级约束:稳定来源 ID 原样保存、`name_zh` 非空、缺失 Importance 写 `null`。 |
||||
|
- 如后续验收仍坚持“11 个职位”,必须由人工提供第 11 个已批准职位名称;当前无任何已批准依据可推导该职位。 |
||||
|
|
||||
|
## 阻断项 |
||||
|
|
||||
|
无。 |
||||
@ -0,0 +1,98 @@ |
|||||
|
# W1-T2 独立评审报告 |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。本次评审未修改任何实现文件,仅写入本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
T2 已建立执行契约要求的七张词库表、关键字段、外键、唯一约束、CHECK 约束和两个查询索引;迁移可执行,且未顺带实现 T3 种子数据或 Java 业务代码。未发现阻断 T3 的 Critical 或 Important finding。 |
||||
|
|
||||
|
## 评审对象与依据 |
||||
|
|
||||
|
评审对象: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V35__skill_cloud_taxonomy.sql` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyMigrationTest.java` |
||||
|
|
||||
|
对照依据: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\execution-contract.md` |
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\design.md` |
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\tasks.md` |
||||
|
- T2 RED/GREEN 日志 |
||||
|
- `git diff --cached`、`git diff HEAD` 和 `git diff --check` |
||||
|
- JDK 17 下的独立测试执行结果 |
||||
|
|
||||
|
## 独立检查结果 |
||||
|
|
||||
|
1. **七张表及关键列存在。** V35 第 5、17、28、44、60、77、88 行分别创建 `skill_source_versions`、`skill_industries`、`skill_occupations`、`skill_categories`、`skills`、`skill_aliases`、`occupation_skills`。测试通过真实 `DatabaseMetaData` 查询七个表的全部预期列,见测试第 55-74、223-240 行,不是 SQL 文本断言。 |
||||
|
|
||||
|
2. **外键真实存在。** V35 第 40-41、57、74、85、105-108 行建立行业、来源版本、职位、分类、技能和别名之间的 9 条外键;测试第 86-94 行通过 `getImportedKeys` 逐条验证。 |
||||
|
|
||||
|
3. **唯一约束真实存在。** V35 第 14、25、39、53、71-72、83-84、98 行覆盖来源版本、行业编码、行业内职位编码、职位内分类编码、技能编码、技能来源、别名归一化值、技能别名和职位技能关系;测试第 76-84 行逐项验证唯一索引。 |
||||
|
|
||||
|
4. **CHECK 约束符合契约。** 分类模板限定为 `PROGRAMMING_LANGUAGE`、`FRAMEWORK`、`TOOL`、`GENERAL`,见 V35 第 54-56 行;技能来源限定为 `ESCO`、`O*NET`、`PROJECT`,见第 73 行;关系类型限定为 `ESSENTIAL`、`OPTIONAL`、`PROJECT`,见第 102-104 行。非法模板、非法来源和非法关系类型均通过实际失败插入验证,见测试第 159-169、182-186 行。 |
||||
|
|
||||
|
5. **`importance` 可空且无默认权重。** V35 第 93 行定义 `DECIMAL(4,2)` 且没有 `NOT NULL` 或 `DEFAULT`;第 99-101 行只约束为空或位于 0.00-5.00。测试插入时省略该列并断言查询结果为 `null`,随后验证 `4.25` 可保存,见测试第 133-153 行。 |
||||
|
|
||||
|
6. **中文名称非空。** 行业、职位、分类和技能的中文名称均定义为 `NOT NULL`,见 V35 第 20、32、48、63 行;测试第 176-181 行通过插入 `name_zh=null` 并断言失败。 |
||||
|
|
||||
|
7. **两个查询索引真实存在。** V35 第 109-110 行建立 `(occupation_id, category_id)` 与 `(occupation_id, importance)` 索引;测试第 96-97 行通过数据库元数据验证。`occupation_id + importance` 可被 MySQL 正向或反向扫描用于热度降序查询。 |
||||
|
|
||||
|
8. **迁移范围正确。** `git diff --name-status HEAD` 在迁移和主代码范围内仅显示新增 `V35__skill_cloud_taxonomy.sql` 及新测试文件;V1-V34 未修改,V36 尚不存在,`src/main/java` 无变更,未顺带实现 T3 或 Java 业务代码。 |
||||
|
|
||||
|
9. **测试失败有效性、隔离和清理。** RED 日志第 60-125 行显示 V35 缺失时两项测试分别因缺表失败;测试使用独立的 `integrationdb_skill_taxonomy` H2 数据库,见第 32-33 行;`@AfterEach` 按外键逆序清理插入数据,见第 196-221 行。未发现仅源码文本断言、共享默认 H2 库污染或明显漏清理。 |
||||
|
|
||||
|
10. **MySQL/H2 兼容性。** `AUTO_INCREMENT`、InnoDB、`utf8mb4_unicode_ci`、`DATETIME DEFAULT/ON UPDATE CURRENT_TIMESTAMP`、表内 `KEY`、CHECK 和命名外键均属于 MySQL 8.4 支持的语法;H2 在 `MODE=MySQL` 下已成功执行整份迁移。未发现仅 H2 成立的 DDL 结构。由于本机没有 Docker,且本地 MySQL 服务无可用测试凭据,本报告未进行真实 MySQL 样库执行;该验证限制不阻断仅要求 H2 迁移测试的 T2。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
执行环境与命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -Dtest=SkillTaxonomyMigrationTest test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Java version: 17.0.16 |
||||
|
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 36.770 s |
||||
|
``` |
||||
|
|
||||
|
提供的 GREEN 日志与独立执行结果一致:JDK 17.0.16、2 项测试全部通过、`BUILD SUCCESS`。RED 日志也证明测试确实依赖真实 V35 迁移结果。 |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
1. **迁移测试只断言列存在,不直接断言全部列类型和可空性。** 测试第 223-240 行收集的是列名;当前 DDL 的类型和可空性经人工核对符合 design/契约,因此不阻断,但未来类型回归不会全部被测试发现。建议后续在数据库元数据断言中补充 `importance DECIMAL(4,2) nullable`、各 `name_zh NOT NULL` 及关键 ID 列类型检查。 |
||||
|
|
||||
|
2. **`importance` 的越界分支未行为测试。** 测试覆盖了 `null` 和 `4.25`,但未尝试 `-0.01` 或 `5.01`,因此当前 CHECK 上下界只经人工核对,见 V35 第 99-101 行。风险较低,可在 T3 或端到端回归中补充。 |
||||
|
|
||||
|
3. **约束失败断言捕获所有 `DataAccessException`,未锁定具体约束。** 测试第 330-332 行通过这一点;当前各失败用例的其余字段均合法,因此实际原因基本可确定,但测试信息量和诊断精度略低。建议后续断言约束名或 SQL 状态/错误码,进一步防止因无关错误而误通过。 |
||||
|
|
||||
|
4. **测试文件末尾存在多余空白行,`git diff --check` 报告 `new blank line at EOF`。** 证据为测试文件第 334 行。纯格式问题,不阻断 T3。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
- 本报告仅在 H2 `MODE=MySQL` 下实际执行了迁移;真实 MySQL 8.4 执行应在已有 MySQL 凭据或 Docker 环境中由后续发布级验证补充。静态检查未发现 MySQL 不兼容语法。 |
||||
|
- 分类必须属于职位属于服务层校验责任,T2 只提供基础外键;该跨列一致性须在 T4/T8 按 design 第 117、170 行落实并验证。 |
||||
|
|
||||
|
## 阻断项 |
||||
|
|
||||
|
无。 |
||||
@ -0,0 +1,136 @@ |
|||||
|
# W1-T3 最终独立复审报告(DP-5 唯一复审) |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。本轮只读取实现、V35/V36、期望映射、证据快照和前序评审报告,并独立执行指定测试、CSV 来源审计与文件哈希核验;未修改任何实现或测试文件,仅写入本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
第三轮 findings 已全部关闭,允许进入 T4。未发现 Critical 或 Important 阻断项。 |
||||
|
|
||||
|
## 独立测试输出 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Java:OpenJDK 17.0.16 |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
SkillCloudSeedDataIntegrationTest: Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
Total: Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 39.479 s |
||||
|
``` |
||||
|
|
||||
|
## 第三轮 Finding 逐项关闭状态 |
||||
|
|
||||
|
### 1. 47/47 条 O*NET 技能来源逐技能精确锁定:关闭 |
||||
|
|
||||
|
- `skill-cloud-expected-mappings.csv` 含 47 条 `SKILL_SOURCE` + `O*NET` 记录,47 个技能编码和 47 个来源三元组均唯一。 |
||||
|
- `verifyExpectedMappings` 先通过 `assertSameMappingKeys` 精确比较期望文件与数据库的全部实体键;随后对每一条执行 `assertMappingMatches`,逐条比较 `source_type` 和 `source_code`。 |
||||
|
- `verifySkillEvidence` 逐条将 47 个 `skill_code -> SOC|Workplace Example|Element ID` 与证据快照核对,并用 `assertEvidenceKeySet` 要求期望软件来源集合与快照 47 条集合完全相等。 |
||||
|
- 交换任意两个 O*NET 技能来源时,至少一行会在逐字段断言处失败,不再存在仅验证“来源三元组集合合法”的缺口。 |
||||
|
- `assertMappingVerifierRejectsTampering` 的交换来源自检通过,且它调用的是全量循环实际使用的同一个比较函数。 |
||||
|
|
||||
|
### 2. 137/137 条非空 Importance 全字段精确断言:关闭 |
||||
|
|
||||
|
- 期望文件包含 139 条职位技能关系,其中 137 条非空 Importance、2 条空 Importance。 |
||||
|
- 137 条非空关系均包含非空 `expected_source_code`、`expected_element_id`、`expected_importance`,且 `expected_is_proxy=1`。 |
||||
|
- 全量循环对每条关系精确比较 `source_type`、`source_code`、`element_id`、`importance` 和 `importance_is_proxy`;数值使用 `0.001` 容差,其余字段使用严格相等。 |
||||
|
- 任意一条非空关系的 proxy 从 `1` 改为 `0`、来源 SOC/Element ID 被替换、或数值被修改,都会在对应关系上失败。 |
||||
|
- 2 条空 Importance 关系均断言来源 SOC、Element ID、Importance 为空且 `importance_is_proxy=0`。 |
||||
|
|
||||
|
### 3. expected-mappings 覆盖且仅覆盖 10/10 occupation、72/72 skill、139/139 relation:关闭 |
||||
|
|
||||
|
- 期望文件共 221 条数据行:`OCCUPATION_SOURCE=10`、`SKILL_SOURCE=72`、`RELATION=139`。 |
||||
|
- `record_type + entity_key` 重复数:0。 |
||||
|
- `assertExpectedMappingCounts` 锁定三类记录数量;`assertSameMappingKeys` 进一步比较期望与数据库完整键集合,缺项和多余项都会失败。 |
||||
|
- 独立测试通过证明 Flyway 实际生成的 10 个职位、72 个技能和 139 条关系与期望文件一一对应。 |
||||
|
|
||||
|
### 4. expected-mappings 与证据快照交叉一致,且 reference 匹配官方 O*NET 31.0 CSV:关闭 |
||||
|
|
||||
|
独立解析结果: |
||||
|
|
||||
|
| 检查项 | 结果 | |
||||
|
| --- | ---: | |
||||
|
| `onet-31-reference.csv` 数据行 | 92 | |
||||
|
| SOFTWARE 证据 | 47,键无重复 | |
||||
|
| IMPORTANCE 唯一证据 | 45,键无重复 | |
||||
|
| 47/47 SOFTWARE 在官方 `software_skills.csv` 精确命中 | 通过 | |
||||
|
| 45/45 IMPORTANCE 在声明官方 CSV 中精确命中 SOC、Element ID、Scale ID=IM、Data Value | 通过 | |
||||
|
| 137/137 加权关系的证据键和值可回溯到 reference | 通过 | |
||||
|
| 期望文件引用的 SOFTWARE 与 reference SOFTWARE 集合相等 | 通过 | |
||||
|
| 期望文件引用的唯一 IMPORTANCE 键与 reference IMPORTANCE 集合相等 | 通过 | |
||||
|
|
||||
|
`target/onet-31` 当前 CSV 哈希: |
||||
|
|
||||
|
```text |
||||
|
software_skills.csv 6AABB96B464288DB849580E6510530EFFF3F80E25DDE2BB23F1FC36BB526B016 |
||||
|
work_activities.csv 6C266205B2926C0C854A7A577AAE29FE492C71BB0DF7E197ADCA830B58FE4EA1 |
||||
|
knowledge.csv 88092E17D16096CDEBE292FEE4389DEB460EDDA24AE8B4637212C69C71562F55 |
||||
|
essential_skills.csv 68C4D31D8C55077238395DCA40CC4E6D32E6A608B5474BB8264B1F98AE415BD7 |
||||
|
transferable_skills.csv F17D0419E818A3BF2F55076510C76C4AEF01CC8F5AEE2670D2F44F6F44AC60F7 |
||||
|
occupation_data.csv A09EAE1D6609686E44E05B7290993A1C8B523D8CA224BC0EEDC194C855C3EE02 |
||||
|
``` |
||||
|
|
||||
|
这些 CSV 哈希与前次独立审计记录的 O*NET 31.0 官方发布包解压文件哈希一致。本轮再次请求官方 ZIP 时收到 HTTP 200,但连接在完成前中断;因此未重复完成 ZIP 级哈希计算。该限制不影响本轮映射验证,因为 reference 已与哈希不变的官方 CSV 逐行精确匹配。 |
||||
|
|
||||
|
### 5. 测试独立性、无 target 依赖、可证伪性:关闭 |
||||
|
|
||||
|
- `SkillCloudSeedDataIntegrationTest` 只使用 `ClassPathResource` 读取 `skill-cloud/onet-31-reference.csv` 和 `skill-cloud/skill-cloud-expected-mappings.csv`。 |
||||
|
- 测试源码未引用 `V36` 文件、`target/onet-31`、文件系统绝对路径或其他运行后才生成的期望数据。 |
||||
|
- 期望映射是独立静态资源;数据库实际值通过 Flyway 迁移和 JDBC 读取,不是从 V36 文本反推后生成期望文件。 |
||||
|
- 测试可在没有 `target/onet-31` 的前提下运行;`target/onet-31` 仅在本次人工来源审计中用于交叉核对。 |
||||
|
- 自检覆盖来源交换、proxy 翻转和 Importance 篡改,且生产断言路径已对全部 221 条期望实体执行。 |
||||
|
|
||||
|
### 6. 68/68 分类、8 个唯一 ESCO occupation URI、混合来源契约及 V35/V36 约束:关闭 |
||||
|
|
||||
|
- 测试 `seedDataCoversIndustriesOccupationsAndEveryDeclaredCategory` 通过:3 个行业、10 个职位、68 个分类,10 个职位的分类清单逐项一致,每个分类至少关联 1 条技能关系,每个职位至少 10 个加权技能。 |
||||
|
- 10 个职位均使用 ESCO 职位来源;8 个 URI 值唯一,均符合 `http://data.europa.eu/esco/occupation/...` 格式。DevOps 与量化开发按已批准契约复用软件开发职位 URI。 |
||||
|
- 混合来源契约保持明确:25 个技能为 ESCO,47 个具体技术为 O*NET `software_skills` 补充;`skills.source_type`、来源三元组唯一约束和来源版本外键一致。 |
||||
|
- V35 仍具备所需唯一约束、外键、查询索引、`importance` 0–5 CHECK、非空 Importance 来源完整性 CHECK 和 proxy `0/1` CHECK。 |
||||
|
- V36 的 137 条非空 Importance 均保存 O*NET SOC、Element ID 和 proxy=1;2 条无可靠映射的关系保留技能并写入 NULL,proxy=0。 |
||||
|
- H2 `MODE=MySQL` 集成迁移测试通过;本轮未新增真实 MySQL 8.4 执行要求。 |
||||
|
|
||||
|
## 文件哈希 |
||||
|
|
||||
|
```text |
||||
|
A9399B4C1D9525F3ED91694FE8F02433C1BF634B458C693D91A4014C1252183B V35__skill_cloud_taxonomy.sql |
||||
|
0D5D39A556B6D72CDB21422ED7020B1EAEC0BE8661635B497D12B40D9D104724 V36__skill_cloud_seed.sql |
||||
|
2A281B099AF4DB5E8117A99D8281191EBC60E68B4BC0E153551AD2013B0071A5 SkillCloudSeedDataIntegrationTest.java |
||||
|
4672287B3197784E4144EE36C322AD27AB3BD6955D6626D885E31349C9A19435 onet-31-reference.csv |
||||
|
F6D7545F01C18A6C13D17C00A051D757AED4A8BD2BEBA34757D5DEC1403A6C7B skill-cloud-expected-mappings.csv |
||||
|
``` |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
- 官方发布包 ZIP 本轮重下载未完成,reference 的官方身份依赖“官方 CSV 哈希不变 + 前次已建立的官方 ZIP 审计记录”。当前不影响 T3 正确性或 T4 放行。 |
||||
|
- 测试不会联网重新校验官方 CSV 哈希;这是有意的离线设计,但后续若更新 reference,仍需人工保留来源审计记录。 |
||||
|
|
||||
|
## 是否允许进入 T4 |
||||
|
|
||||
|
**允许。** 第三轮两项 Important finding 均已关闭,10/10 测试通过,未发现新的 Critical 或 Important 问题。 |
||||
@ -0,0 +1,207 @@ |
|||||
|
# W1-T3 最终独立复审报告(第二轮修复) |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。本次复审只读取实现、迁移、来源清单、前两轮报告和本地/官方 CSV,并独立执行指定测试、来源审计及 ESCO URI 只读核验;未修改任何实现文件,仅写入本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**FAIL** |
||||
|
|
||||
|
第二轮唯一 Important finding 尚未完全关闭: |
||||
|
|
||||
|
- 47 条 O*NET `software_skills` 来源当前数据全部真实,但测试只验证“每条 DB 三元组存在于快照集合中”,没有验证“每个技能代码对应唯一正确的三元组”;交换两个未被单点断言的技能来源后,测试仍会通过。 |
||||
|
- 137 条非空 Importance 的值均已逐条核对,但测试对 `importance_is_proxy` 仍主要是 `0/1` 枚举检查,仅 10 条做 `proxy=1` 精确断言;将其余 127 条中的任一 `1` 改为 `0` 后,测试仍会通过。 |
||||
|
|
||||
|
以上属于回归保护缺口,不阻断当前种子数据的正确性,但按 TO-12 和本轮“篡改必须失败”的要求,仍阻断 T4。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
执行: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
SkillCloudSeedDataIntegrationTest: Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
Total: Tests run: 9, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 39.800 s |
||||
|
``` |
||||
|
|
||||
|
## 来源审计方法与结果 |
||||
|
|
||||
|
### 1. O*NET 31.0 官方来源核验 |
||||
|
|
||||
|
独立下载官方发布包: |
||||
|
|
||||
|
- URL:`https://www.onetcenter.org/dl_files/database/db_31_0_csv.zip` |
||||
|
- HTTP:`200` |
||||
|
- 文件大小:`16,237,378` bytes |
||||
|
- HTTP `Last-Modified`:`Tue, 25 Aug 2026 03:46:00 GMT` |
||||
|
- 官方 ZIP SHA-256:`55033FC68B4C13EC23E7F74DC6378660F6E854E75D55D6E333AE0A761D3987CD` |
||||
|
|
||||
|
从官方 ZIP 解压后与 `mydreamjob-server/target/onet-31` 逐文件比较,SHA-256 完全一致: |
||||
|
|
||||
|
| 文件 | SHA-256 | |
||||
|
| --- | --- | |
||||
|
| `software_skills.csv` | `6AABB96B464288DB849580E6510530EFFF3F80E25DDE2BB23F1FC36BB526B016` | |
||||
|
| `work_activities.csv` | `6C266205B2926C0C854A7A577AAE29FE492C71BB0DF7E197ADCA830B58FE4EA1` | |
||||
|
| `knowledge.csv` | `88092E17D16096CDEBE292FEE4389DEB460EDDA24AE8B4637212C69C71562F55` | |
||||
|
| `essential_skills.csv` | `68C4D31D8C55077238395DCA40CC4E6D32E6A608B5474BB8264B1F98AE415BD7` | |
||||
|
| `transferable_skills.csv` | `F17D0419E818A3BF2F55076510C76C4AEF01CC8F5AEE2670D2F44F6F44AC60F7` | |
||||
|
| `occupation_data.csv` | `A09EAE1D6609686E44E05B7290993A1C8B523D8CA224BC0EEDC194C855C3EE02` | |
||||
|
|
||||
|
因此,`target/onet-31` 中的本轮 CSV 来源与官方 O*NET 31.0 发布包一致,不是本地改写版本。 |
||||
|
|
||||
|
### 2. 证据快照与官方 CSV 对照 |
||||
|
|
||||
|
`mydreamjob-server/src/test/resources/skill-cloud/onet-31-reference.csv`: |
||||
|
|
||||
|
- 总行数:92 条数据行。 |
||||
|
- `SOFTWARE`:47 条,47 个唯一键,无重复。 |
||||
|
- `IMPORTANCE`:45 条唯一 `SOC + Element ID` 组合,无重复。 |
||||
|
- 47/47 条 `SOFTWARE` 三元组均精确命中 `software_skills.csv`。 |
||||
|
- 45/45 条 `IMPORTANCE` 均命中声明的官方 CSV,`Data Value` 精确一致。 |
||||
|
- 未发现无依据行、伪造三元组或伪造数值。 |
||||
|
- `SOFTWARE` 的 `data_value` 均为空,符合 `software_skills` 没有 Importance 值的结构。 |
||||
|
|
||||
|
结论:当前证据快照确实来自 O*NET 31.0 官方 CSV,数据与官方 CSV 完全一致。 |
||||
|
|
||||
|
### 3. V36 数据全量审计 |
||||
|
|
||||
|
独立解析 V36 后确认: |
||||
|
|
||||
|
| 项目 | 结果 | |
||||
|
| --- | ---: | |
||||
|
| O*NET 技能 | 47 | |
||||
|
| DB 使用的 SOFTWARE 三元组集合与快照集合相等 | 是 | |
||||
|
| 职位技能关系 | 139 | |
||||
|
| 非空 Importance | 137 | |
||||
|
| 空 Importance | 2 | |
||||
|
| 非空关系正确命中快照键和值 | 137/137 | |
||||
|
| 非空关系当前 `importance_is_proxy=1` | 137/137 | |
||||
|
| 空关系的 SOC/Element ID 为 NULL、proxy=0 | 2/2 | |
||||
|
| 137 条关系使用的唯一 Importance 键 | 45 | |
||||
|
| 快照 IMPORTANCE 键集合与实际使用集合相等 | 是 | |
||||
|
|
||||
|
结论:当前数据层没有发现伪造来源、错误数值或错误代理标记。 |
||||
|
|
||||
|
### 4. Manifest 分类覆盖 |
||||
|
|
||||
|
从 `source-manifest.md` 独立解析 10 个职位及声明分类,并与 V36 对比: |
||||
|
|
||||
|
- 职位:10/10 一致。 |
||||
|
- 分类:68/68 存在。 |
||||
|
- 每个声明分类至少关联 1 条技能关系;本轮独立统计最小值为 2、最大值为 3。 |
||||
|
- 未发现分类遗漏或职位—分类清单不一致。 |
||||
|
|
||||
|
### 5. ESCO 职位 URI |
||||
|
|
||||
|
V36 与测试中的 10 个职位映射一致,其中 8 个唯一 ESCO occupation URI。对 8 个 URI 调用 ESCO 官方只读 API,全部返回 HTTP `200`: |
||||
|
|
||||
|
- 前端开发工程师 URI |
||||
|
- 后端开发工程师 URI |
||||
|
- 测试工程师 URI |
||||
|
- 金融数据分析师 URI |
||||
|
- 风控建模工程师 URI |
||||
|
- 教育产品经理 URI |
||||
|
- 教学设计师 URI |
||||
|
- 教育数据分析师 URI |
||||
|
|
||||
|
DevOps、量化开发复用软件开发者 URI 的项目代理关系已在 V36 注释中明确,和当前批准清单一致。 |
||||
|
|
||||
|
## 上一轮 Finding 关闭状态 |
||||
|
|
||||
|
| 第一轮/第二轮 Finding | 状态 | 证据 | |
||||
|
| --- | --- | --- | |
||||
|
| C-1:混合来源未经批准 | 关闭 | 契约、design、source manifest 均已明确 ESCO 主来源 + O*NET `software_skills` 补充;V35/V36 字段语义与修订契约一致。 | |
||||
|
| I-1:代理 SOC/ESCO URI 不可追溯 | 关闭 | 10 个职位均有 ESCO source version 和 URI;8 个唯一 URI 独立返回 200;代理 SOC、Element ID、proxy 均落表。 | |
||||
|
| I-2:manifest 分类遗漏 | 关闭 | 68/68 分类存在,每个分类至少 1 条关系,清单与 V36 一致。 | |
||||
|
| I-3a:Importance 逐条可追溯数据 | 关闭 | V35 增加来源字段;V36 的 137/137 非空关系均匹配官方快照,当前数据真实。 | |
||||
|
| I-3b:测试全量覆盖 47/47 软件来源 | **部分关闭** | 47 条都做集合成员验证,但只有 5 条做 `skillCode -> sourceCode` 精确映射断言;未断言的两个技能交换来源仍可通过。 | |
||||
|
| I-3c:测试全量覆盖 137/137 非空 Importance | **部分关闭** | 137 条都验证来源键和数值;但 `proxy` 只对 10 条精确断言,其余 127 条可在 1 和 0 之间翻转而不失败。 | |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Important-1:47 条 O*NET 软件来源未做完整的逐技能精确映射断言 |
||||
|
|
||||
|
位置: |
||||
|
|
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudSeedDataIntegrationTest.java:160-183` |
||||
|
- 精确映射单点断言:`185-189` |
||||
|
- 辅助断言:`474-478` |
||||
|
|
||||
|
问题: |
||||
|
|
||||
|
当前循环对每条 DB `source_code` 只检查其三元组是否存在于快照中,再要求 DB 行数与快照行数相等。它验证了“集合完整”,但没有验证每个技能代码必须绑定哪个三元组。 |
||||
|
|
||||
|
可证伪反例: |
||||
|
|
||||
|
- 任意交换两个未被 `assertOnetSkillSource` 覆盖的 O*NET 技能来源,例如 `SPRING_BOOT` 与 `DJANGO`。 |
||||
|
- DB 仍然有 47 个唯一且合法的三元组,集合仍与快照相等。 |
||||
|
- 现有 5 条单点断言不涉及这两个技能,因此测试仍通过,但技能溯源已经被错误交换。 |
||||
|
|
||||
|
影响: |
||||
|
|
||||
|
`skills.source_code` 是来源追踪字段。来源交换会静默破坏“技能到上游来源 ID”的准确性,当前回归测试无法阻止。 |
||||
|
|
||||
|
修复要求: |
||||
|
|
||||
|
- 在快照中增加 `skill_code`(仅 SOFTWARE 行使用),或新增完整的 `skillCode -> SOC|Workplace Example|Element ID` 映射资源。 |
||||
|
- 对 47/47 条 O*NET 技能逐条断言精确来源,而不是仅做集合成员检查。 |
||||
|
|
||||
|
### Important-2:Importance Proxy 标记未做全量语义断言 |
||||
|
|
||||
|
位置: |
||||
|
|
||||
|
- 枚举检查:`SkillCloudSeedDataIntegrationTest.java:201-203` |
||||
|
- 循环内代理检查:`229-230` |
||||
|
- 仅 10 条精确断言:`271-294` |
||||
|
- 精确断言辅助方法:`481-494` |
||||
|
|
||||
|
问题: |
||||
|
|
||||
|
`assertTrue(proxy == 0 || proxy == 1)` 只证明值处于合法范围,不能证明它符合种子数据的来源语义。V36 当前明确要求 137 条非空 Importance 全部是跨来源代理,必须为 `1`;测试只对 10 条做 `proxy=1` 断言。 |
||||
|
|
||||
|
可证伪反例: |
||||
|
|
||||
|
- 将任意一个未被 `assertImportance` 覆盖的非空关系从 `importance_is_proxy=1` 改为 `0`。 |
||||
|
- 该行仍满足数据库 CHECK 和测试的 `0/1` 判断,SQL 计数也不检查非空代理必须为 1,因此测试仍通过。 |
||||
|
- `importance`、SOC、Element ID 和 Data Value 均未发生变化,但来源语义已经被篡改。 |
||||
|
|
||||
|
影响: |
||||
|
|
||||
|
代理标记是来源审计字段。当前测试不能保证 137 条全部保持已批准的 proxy 语义,无法满足“篡改 Proxy 标记必须失败”的要求。 |
||||
|
|
||||
|
修复要求: |
||||
|
|
||||
|
- 明确断言所有非空 Importance 的 `importance_is_proxy=1`,以及所有空 Importance 的 `importance_is_proxy=0`;或为 137 条关系提供逐关系期望映射。 |
||||
|
- 若业务允许直接 O*NET Importance 与代理 Importance 并存,需要按关系保存期望值并逐条断言。 |
||||
|
|
||||
|
### Minor-1:快照的官方来源属性依赖评审时外部核验,未被自动化锁定 |
||||
|
|
||||
|
`loadEvidence` 只验证快照头部、列数、允许的 `source_file` 名称和键唯一性,不会读取官方 CSV 或校验官方发布包哈希。 |
||||
|
|
||||
|
当前快照已由本次复审用官方 ZIP 哈希确认,因此不阻断;但未来若快照的 `source_file` 被改成同一允许集合内的另一个文件,只要测试资源本身同步修改,现有测试无法识别来源文件标签变化。建议在来源文档或测试资源旁记录官方 ZIP/CSV 哈希,作为后续审计基线。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
- T3 当前数据层已通过,不能因为数据正确就忽略 Important-1 和 Important-2 的回归缺口。 |
||||
|
- 真实 MySQL 8.4 尚未执行;本轮仅按要求执行 H2 `MODE=MySQL`。该限制沿用前轮,不构成本次新的阻断项。 |
||||
|
- V35/V36 当前满足修订后的混合来源契约;未发现需要再次 Rewind 的契约冲突。 |
||||
|
|
||||
|
## 阻断项 |
||||
|
|
||||
|
T3 仍为 **FAIL**。在补齐 47/47 逐技能精确来源映射和 137/137 Importance 代理语义断言前,不应进入 T4。当前测试 9/9 通过只能证明现有数据与当前快照一致,不能证明上述两类篡改必然失败。 |
||||
@ -0,0 +1,158 @@ |
|||||
|
# W1-T3 独立复审报告(聚焦修复) |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。仅读取实现、契约、来源数据与本地 O*NET CSV,并独立执行测试和来源审计;未修改任何实现文件,仅写入本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**FAIL** |
||||
|
|
||||
|
第一轮 Critical 已关闭,I-1、I-2 的当前数据问题已关闭;I-3 的“逐条可追溯数据”已关闭,但对应的回归测试仍未关闭:当前测试只精确断言 5/47 条 O*NET 技能来源和 10/137 条非空 Importance,且没有任何 CSV 驱动的来源完整性测试。多数不存在或伪造的 O*NET 行、错误 Importance 仍会通过测试。该项属于 Important,阻断 T4。 |
||||
|
|
||||
|
## 评审对象与依据 |
||||
|
|
||||
|
实现文件: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V35__skill_cloud_taxonomy.sql` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V36__skill_cloud_seed.sql` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyMigrationTest.java` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillCloudSeedDataIntegrationTest.java` |
||||
|
|
||||
|
依据: |
||||
|
|
||||
|
- 修订后的 `execution-contract.md`、`design.md`、`tasks.md` |
||||
|
- `changes/n03-skill-cloud/data/source-manifest.md` |
||||
|
- `docs/skill-taxonomy-sources.md` |
||||
|
- 第一轮报告 `changes/n03-skill-cloud/.superpowers/sdd/reviews/W1-T3.md` |
||||
|
- 修复报告日志 `mydreamjob-server/target/skill-taxonomy-fix-green.log` |
||||
|
- 本地 O*NET 31.0 CSV 与 `changes/n03-skill-cloud/data/esco-skill-lookup.json` |
||||
|
- ESCO 官方只读 API(用于职位 URI 真实性核验) |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
执行: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
SkillCloudSeedDataIntegrationTest: Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
Total: Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 40.195 s |
||||
|
``` |
||||
|
|
||||
|
## 第一轮 Finding 关闭状态 |
||||
|
|
||||
|
| 第一轮 Finding | 状态 | 独立证据 | |
||||
|
| --- | --- | --- | |
||||
|
| C-1:混合来源未授权,职位与关系来源被替换 | **关闭** | `execution-contract.md:7,20,56` 已明确授权“ESCO 主来源 + O*NET `software_skills` 补充”;`design.md:32-43` 与 `source-manifest.md:35-43` 同步;V36 中 10 个职位均使用 ESCO occupation URI,25 条 ESCO 技能和 47 条 O*NET 补充技能分源导入。 | |
||||
|
| I-1:金融/风控/教育产品经理代理映射不明确或 URI 不真实 | **关闭** | V36:6-28 明确代理 SOC;V35:93-96 将目标来源与 Importance 来源拆列;V36:274-413 的 137 条非空热度均设置 `importance_is_proxy=1`。独立调用 ESCO 官方 API,10 个职位行对应的 8 个唯一 URI 全部返回 200,标题分别为 web developer、software developer、software tester、financial analyst、risk manager、ICT product manager、instructional designer、data analyst。 | |
||||
|
| I-2:manifest 分类遗漏,测试只要求至少一个分类 | **当前数据关闭** | manifest 10 个职位的分类清单与 V36:59-127 的实际 68 个分类逐项一致,测试也已改为精确比较完整清单;独立解析确认 68/68 分类至少关联 1 条技能。剩余测试弱点见 Minor-1。 | |
||||
|
| I-3:Importance 缺少逐技能来源映射和有效验证 | **部分关闭,仍为 Importance 阻断** | V35:94-110 已增加 SOC、Element ID、代理标记和完整性约束;V36:274-413 的 137/137 条非空热度均带完整字段,并可精确核对 CSV。但测试只覆盖少量样本,不能阻止多数来源或热度被篡改;详见 Important-1。 | |
||||
|
|
||||
|
## 来源审计方法与数量 |
||||
|
|
||||
|
### 1. SQL 结构审计 |
||||
|
|
||||
|
独立解析 V36 的 `INSERT` 行,结果: |
||||
|
|
||||
|
| 对象 | 数量 | |
||||
|
| --- | ---: | |
||||
|
| 来源版本 | 2 | |
||||
|
| 行业 | 3 | |
||||
|
| 职位 | 10 | |
||||
|
| 技能分类 | 68 | |
||||
|
| 技能 | 72(ESCO 25,O*NET 47) | |
||||
|
| 技能别名 | 72 | |
||||
|
| 职位技能关系 | 139 | |
||||
|
| 非空 Importance | 137 | |
||||
|
| 空 Importance | 2 | |
||||
|
| 每个职位非空 Importance 最小值 | 12 | |
||||
|
|
||||
|
V36 仅有 7 条 `INSERT` 语句,没有 `CREATE`、`ALTER`、`DROP`、`UPDATE`、`DELETE`。`com.mydreamjob.skill` 主代码包不存在,未提前实现 T4;V1-V34 没有改动。 |
||||
|
|
||||
|
### 2. ESCO 来源审计 |
||||
|
|
||||
|
- 25/25 条 `source_type='ESCO'` 技能的 URI 都能在 `esco-skill-lookup.json` 中找到。 |
||||
|
- 10/10 个职位行的 `source_version_id=1`,职位来源均为 ESCO occupation URI。 |
||||
|
- 10 个职位使用 8 个唯一 ESCO occupation URI;`BACKEND_DEVELOPER`、`DEVOPS_ENGINEER`、`QUANTITATIVE_DEVELOPER` 复用 software developer URI,V36:47 已明确标注为项目代理。 |
||||
|
- 对 8 个唯一 URI 独立调用 `https://ec.europa.eu/esco/api/resource/occupation?uri=...&language=en`,全部 HTTP 200,返回的 URI 与请求一致。 |
||||
|
|
||||
|
### 3. O*NET `software_skills` 审计 |
||||
|
|
||||
|
- 使用本地 `mydreamjob-server/target/onet-31/software_skills.csv`(31,821 行)。 |
||||
|
- 47/47 条 O*NET 技能的 `(O*NET-SOC Code, Workplace Example, Element ID)` 三元组精确命中 CSV,无缺失或伪造组合。 |
||||
|
- 当前测试对这 47 条来源只有正则格式检查,另精确断言的来源样本为 5 条。 |
||||
|
|
||||
|
### 4. Importance 审计 |
||||
|
|
||||
|
- 使用本地 `work_activities.csv`、`knowledge.csv`、`essential_skills.csv`、`transferable_skills.csv`,共解析出 99,231 个 `(SOC, Element ID, Data Value)` 的 `IM/Importance` 键。 |
||||
|
- 137/137 条非空 Importance 均可按 `(importance_source_code, importance_element_id, importance)` 精确命中至少一个 CSV。 |
||||
|
- 137/137 条均满足 `importance_source_code` 和 `importance_element_id` 非空,且 `importance_is_proxy=1`。 |
||||
|
- 2/2 条空 Importance 的 SOC、Element ID 均为 NULL,`importance_is_proxy=0`。 |
||||
|
- 当前测试对 137 条非空热度中的 10 条做精确值断言,其余 127 条只检查字段存在,不检查数值是否真实。 |
||||
|
|
||||
|
## 阻断项 |
||||
|
|
||||
|
### Important-1:回归测试不能覆盖大多数 O*NET 来源与 Importance 篡改 |
||||
|
|
||||
|
**位置** |
||||
|
|
||||
|
- `SkillCloudSeedDataIntegrationTest.java:132-139`:47 条 O*NET 来源只验证正则格式。 |
||||
|
- `SkillCloudSeedDataIntegrationTest.java:141-145`:仅精确断言 5 条 O*NET `software_skills` 行。 |
||||
|
- `SkillCloudSeedDataIntegrationTest.java:152-163`:只验证非空字段是否存在及来源版本名称是否为 O*NET。 |
||||
|
- `SkillCloudSeedDataIntegrationTest.java:165-187`:仅精确断言 10 条 Importance 及 2 条 NULL。 |
||||
|
|
||||
|
**独立量化** |
||||
|
|
||||
|
- O*NET 技能总数为 47,精确来源断言为 5,剩余 42 条不存在“CSV 行必须存在”的回归保护。 |
||||
|
- 非空 Importance 总数为 137,精确值断言为 10,剩余 127 条即使被改成任意 0-5 的数值,现有测试仍会通过。 |
||||
|
- 测试文件未读取 `software_skills.csv`、`work_activities.csv`、`knowledge.csv`、`essential_skills.csv` 或 `transferable_skills.csv`,因此无法证明“当前值来自 O*NET 31.0 CSV”。 |
||||
|
|
||||
|
**影响** |
||||
|
|
||||
|
当前数据库中的 47/47 技能来源和 137/137 Importance 已由本次复审全量核对为真实;但数据正确性只存在于当前 SQL 快照,没有被自动化测试保护。未来修改若把未抽样的来源替换为格式合法但不存在的三件套,或把未抽样的 Importance 改成错误值,测试仍会通过,随后直接影响词云排序和匹配结果。 |
||||
|
|
||||
|
**修复方向** |
||||
|
|
||||
|
- 增加覆盖全部 47 条 O*NET 技能来源的精确来源清单或 CSV 驱动断言。 |
||||
|
- 增加覆盖全部 137 条非空 Importance 的精确映射断言;若 CSV 不进入正常测试运行环境,至少提交一份受版本控制的完整映射基线,并校验行数、键集合和值。 |
||||
|
- 对 137/137 条映射逐项验证 `SOC + Element ID + Data Value`,而不是只验证非空和应用层来源版本。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
### Minor-1:每个分类至少关联一个技能未进入断言 |
||||
|
|
||||
|
`SkillCloudSeedDataIntegrationTest.java:89-113` 验证完整分类清单,但只对职位的技能总数和非空热度总数要求 `>= 10`,没有验证每个分类的关联技能数 `>= 1`。当前 68/68 分类均通过独立数据审计,因此不阻断;后续若新增或删改分类,建议补一条分组断言。 |
||||
|
|
||||
|
### Minor-2:Importance 越界值测试仍缺失 |
||||
|
|
||||
|
V35:102-104 定义了 0-5 的 CHECK,但 T3 测试覆盖的是 provenance 缺失、代理标记非法和成功带来源更新,没有直接断言 `importance < 0` 或 `> 5` 在 H2 `MODE=MySQL` 下失败。MySQL 8.4 会执行 CHECK,当前没有发现生产不可兼容语法,但真实 MySQL 尚未在本次环境中执行。 |
||||
|
|
||||
|
### Minor-3:执行计划中的契约哈希未同步修订版 |
||||
|
|
||||
|
当前 `execution-contract.md` 的 SHA-256 为 `98d869bc3b7e29b6fefffd973e561be50ba21ef4f59f4146ccd47cf8164957da`,与 `.spec-superflow.yaml` 一致;但 `.superpowers/sdd/execution-plan.yaml` 仍记录旧哈希 `9ae4be4a8561d7f303804c84d75061bbf2c8b3be2e7c79495689c19ba09d028e`。不影响 T3 数据正确性,建议在 W1 复评通过后同步执行计划。 |
||||
|
|
||||
|
## 未阻断的确认项 |
||||
|
|
||||
|
- V35 的新增列、provenance CHECK、代理标记 CHECK 在 H2 `MODE=MySQL` 下通过测试。 |
||||
|
- `importance_source_code`、`importance_element_id` 的字段长度足以容纳当前 O*NET SOC 和 Element ID。 |
||||
|
- V36 保持纯数据迁移,未提前引入 T4 Java 业务代码。 |
||||
|
- 未修改 V1-V34。 |
||||
|
|
||||
|
## 阻断项汇总 |
||||
|
|
||||
|
T3 当前不通过。第一轮 C-1、I-1、I-2 的阻断已关闭,I-3 的当前数据已清除,但对应的自动化回归保护仍不足,必须补齐 Important-1 后才能交给 T4。 |
||||
@ -0,0 +1,155 @@ |
|||||
|
# W1-T3 独立评审报告 |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。本次评审只读取实现、来源数据、契约和日志,并独立执行测试;未修改任何实现文件,仅写入本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**FAIL** |
||||
|
|
||||
|
存在 1 项 Critical 和 3 项 Important。当前 V36 虽然能在 H2 下通过既有测试,但没有落实已批准的“ESCO 负责职位—技能—分类主关系”方案,且部分金融职位使用与目标职位不符的 O*NET SOC 替代来源;同时 T1 清单中的多个技能分类没有导入。上述问题均阻断 T4。 |
||||
|
|
||||
|
## 评审对象与依据 |
||||
|
|
||||
|
评审对象: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V36__skill_cloud_seed.sql` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillCloudSeedDataIntegrationTest.java` |
||||
|
|
||||
|
对照依据: |
||||
|
|
||||
|
- `changes/n03-skill-cloud/execution-contract.md` |
||||
|
- `changes/n03-skill-cloud/design.md` |
||||
|
- `changes/n03-skill-cloud/tasks.md` |
||||
|
- `changes/n03-skill-cloud/data/source-manifest.md` |
||||
|
- `docs/skill-taxonomy-sources.md` |
||||
|
- `V35__skill_cloud_taxonomy.sql` |
||||
|
- 本地 ESCO lookup、O*NET 31.0 CSV、实现者 RED/GREEN 日志 |
||||
|
|
||||
|
## 独立检查结果 |
||||
|
|
||||
|
### 1. 数量与基本约束 |
||||
|
|
||||
|
独立解析和 H2 测试结果一致: |
||||
|
|
||||
|
| 项目 | 结果 | |
||||
|
| --- | ---: | |
||||
|
| 行业 | 3 | |
||||
|
| 职位 | 10 | |
||||
|
| 技能分类 | 50 | |
||||
|
| 技能 | 53 | |
||||
|
| 技能别名 | 53 | |
||||
|
| 职位技能关系 | 137 | |
||||
|
| 非空 Importance | 133 | |
||||
|
| 空 Importance | 4 | |
||||
|
|
||||
|
每个职位均有 10 至 17 条职位技能关系,非空 Importance 数量均不少于 10。全部技能有中文标准名、非空 `source_code` 和至少一个别名;`normalized_alias` 无重复。当前 137 条关系的 `category_id` 均与同一 `occupation_id` 对应,未发现现有种子内的跨职位分类错配。 |
||||
|
|
||||
|
### 2. ESCO 与 O*NET 来源核验 |
||||
|
|
||||
|
- V36 的 25 个 `source_type='ESCO'` URI 均可从 `changes/n03-skill-cloud/data/esco-skill-lookup.json` 找到,英文标题与 lookup 一致。 |
||||
|
- V36 的 28 个 O*NET 技能来源三元组 `O*NET-SOC|Workplace Example|Element ID` 均能在 `software_skills.csv` 精确命中;未发现伪造组合。 |
||||
|
- V36 注释声明的 43 组(SOC、Importance、语义元素)映射值均能在本地 O*NET CSV 中命中。其中 `Scikit-learn` 的当前工作区值已按 CSV 精确大小写修复。 |
||||
|
- lookup 中本身存在明显误匹配,例如 Angular→computer programming、Spring Boot→install deicing boot、Node.js→apply electrolytes to cathodes and anodes、React→react to emergency situations in a live performance environment、Redis→redistribute wagered money、Microsoft Power BI→Microsoft Access。当前 V36 没有使用这些错误 URI,因此它们不是本批次已导入数据的直接错误,但该 lookup 不能作为未经复核的可信自动映射源。 |
||||
|
|
||||
|
### 3. 独立测试 |
||||
|
|
||||
|
执行命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
SkillCloudSeedDataIntegrationTest: Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
Total: Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
该结果与实现者 GREEN 日志一致。 |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
#### C-1:V36 用 O*NET/项目映射替代了已批准的 ESCO 职位—技能—分类主关系 |
||||
|
|
||||
|
证据: |
||||
|
|
||||
|
- 执行契约第 55 行要求“使用 ESCO 作为职位—技能—分类主关系,O*NET Importance 作为 heat”。 |
||||
|
- 设计 D1 第 32-42 行同样规定 ESCO 负责职位—技能—分类关系;来源清单第 34-39、51、60-63 行要求保留原上游来源 ID,并由项目维护跨源重要性映射。 |
||||
|
- V35 第 34 行将 `skill_occupations.source_code` 定义为 `ESCO 职位来源 ID`。 |
||||
|
- V36 第 30-39 行将 10 个职位的 `source_code` 全部写成 O*NET SOC,并将 `source_version_id` 指向 O*NET 来源版本。 |
||||
|
- V36 第 203-340 行 137 条职位技能关系的 `source_version_id` 全部为 O*NET。 |
||||
|
- 本地只有 ESCO 技能 lookup,没有 ESCO 职位—技能关系数据;V36 已导入的 25 个 ESCO 技能只提供了技能 URI,未提供任何 ESCO 职业关系。 |
||||
|
|
||||
|
影响:这不是字段命名的小问题,而是数据治理来源发生实质变化:职位节点被标成 O*NET SOC,职位技能关系改由 O*NET software_skills、项目分类和代理 Importance 人工组装。下游接口即使运行正确,也无法证明返回的是契约批准的 ESCO 主关系。该变化命中执行契约第 136 行的 Rewind 条件“数据来源发生实质变化”。 |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
#### I-1:两个金融职位使用不能代表目标职位的替代 SOC,且替代映射未获批准或写入来源清单 |
||||
|
|
||||
|
证据: |
||||
|
|
||||
|
- V36 第 34 行将“金融数据分析师”写成 `13-2031.00`。本地 `occupation_data.csv` 中该 SOC 的标题是 **Budget Analysts**;原计划目标 `13-2051.00` 是 **Financial and Investment Analysts**。 |
||||
|
- V36 第 35 行将“风控建模工程师”写成 `13-2053.00`。本地 CSV 中该 SOC 的标题是 **Insurance Underwriters**;原计划目标 `13-2054.00` 是 **Financial Risk Specialists**。 |
||||
|
- V36 第 16-17 行仅以 SQL 注释说明替换;`source-manifest.md` 第 22-23 行没有批准代理 SOC,也没有记录替换后的目标语义。 |
||||
|
- `13-2051.00` 和 `13-2054.00` 在本地四份 Importance CSV 中确实没有行,但“原目标无数据”不等于可以用语义不同的职业覆盖其来源 ID。 |
||||
|
|
||||
|
影响:Budget Analysts 与 Insurance Underwriters 的 O*NET 活动分布不能代表金融数据分析师和风控建模工程师。尤其是 Insurance Underwriters 作为风控建模工程师的热度来源,会导致技能热度与目标职位脱节。必须在来源清单和契约中明确代理规则与语义合理性,或重新选择可代表目标职位的来源;不能直接把替代 SOC 写入目标职位的 `source_code`。 |
||||
|
|
||||
|
#### I-2:T3 清单中的多个技能分类未落表,测试只要求至少一个分类因而漏检 |
||||
|
|
||||
|
证据: |
||||
|
|
||||
|
- `source-manifest.md` 第 18-27 行给出了每个职位的首批技能分类范围,第 30 行明确这些分类是“每个职位首批应覆盖的能力域”。 |
||||
|
- V36 第 42-91 行为每个职位固定导入 5 个分类,共 50 个。 |
||||
|
- 前端开发缺少清单中的“浏览器与调试”“测试”。 |
||||
|
- DevOps 缺少“配置管理与 IaC”“可观测性”“安全”等独立能力域。 |
||||
|
- 风控建模缺少“模型验证”等能力域。 |
||||
|
- 教学设计师缺少“无障碍与规范”等能力域。 |
||||
|
- 测试第 70-71 行只断言 `category_count >= 1`,因此即使只导入一个分类也会通过。 |
||||
|
|
||||
|
影响:已批准的 T3 分类覆盖范围没有完整交付,后续接口会缺少部分可筛选分类;当前测试无法阻止该类回归。 |
||||
|
|
||||
|
#### I-3:Importance 缺少逐技能来源映射和有效验证,现有数据大量依赖同值代理 |
||||
|
|
||||
|
证据: |
||||
|
|
||||
|
- V36 第 5-15 行说明所有技术技能使用“Working with Computers”等职业活动代理值,而不是 O*NET software_skills 自身的 Importance。 |
||||
|
- V36 第 203-340 行的 133 条非空关系实际只形成 39 个不同的 `(occupation, importance)` 组合;多个技能共享同一代理值,例如同一职业内多数技术技能全部为同一个值。 |
||||
|
- V35 第 93-95 行只保存 `importance` 和 `source_version_id`,没有保存用于推导热度的 O*NET Element ID;V36 的注释不是机器可追溯的数据。 |
||||
|
- 测试第 106-127 行只精确校验前端 JavaScript 的 `4.88` 和 Figma 的 `null`,其余 132 条重要性记录或错误来源元素不会被发现。 |
||||
|
|
||||
|
影响:词云热度会因大量同值技能而主要退化为 `nameZh ASC` 排序,无法证明每个技能的热度确实来自对应 O*NET 来源。若继续该口径,应新增可追溯的 Importance 映射记录并扩大测试,或者重新定义可接受的代理规则和排序语义。 |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
1. **“45 个 Importance 原值”的核验数量不实。** V36 第 5-15 行声明了 43 个职业/能力域映射,133 条非空关系只有 39 个不同 `(occupation, importance)` 组合;报告中“45 个 Importance 原值”没有可复现证据。这是核验报告计数问题,不代表已发现任意 CSV 伪造值。 |
||||
|
|
||||
|
2. **最终提交可能漏掉 Scikit-learn 大小写修复。** 当前工作区是 `Scikit-learn`,与 CSV 一致;但 `git status` 显示 V36 为 `AM`,即该修复尚未 staged。提交前必须确保修正包含在最终 T3 提交中。 |
||||
|
|
||||
|
3. **测试主要验证行数、非空值和两个单点值,不是完整来源契约测试。** 当前即使将除 JavaScript 外的 Importance 改错、将非空 ESCO/O*NET source code 改成任意非空字符串,或只保留每个职位一个分类,测试仍可能通过。分类跨职位错配也不会被测试发现,因为数据库没有覆盖 `(occupation_id, category_id)` 的复合外键。 |
||||
|
|
||||
|
## 已确认没有问题的部分 |
||||
|
|
||||
|
- V36 是纯数据迁移:只有 7 个 `INSERT`,未包含 `UPDATE/DELETE/DROP/ALTER/CREATE`。 |
||||
|
- `git diff HEAD` 在本任务实现范围内只显示 V36 和 T3 测试;V1-V35 未修改,`src/main/java/com/mydreamjob/skill` 不存在,未提前实现 T4 业务代码。 |
||||
|
- 当前 137 条关系的技能分类均与所属 occupation 对应;所有技能、别名、来源版本和关系均满足 V35 现有外键及唯一约束。 |
||||
|
- 28 个 O*NET 技能来源组合和 25 个 ESCO URI 均可从本地来源文件核实,没有发现伪造 O*NET software row 或伪造 ESCO URI。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
- 必须决定是恢复 ESCO 职位—技能—分类主关系,还是正式修改 design、source manifest 和执行契约,承认以 O*NET SOC 与项目映射作为主关系。后者属于数据来源实质变化,应走 Rewind。 |
||||
|
- 必须确认“代理 Importance”是否属于已批准方案;若属于,需记录每个技能或能力域的 O*NET Element ID、代理理由和质量规则,并重新定义同值排序的产品语义。 |
||||
|
- 若继续使用 `13-2031.00` 和 `13-2053.00`,需要提供可审查的职位语义映射依据并更新来源清单;否则应更换代表 SOC。 |
||||
|
- 本机未进行真实 MySQL 迁移验证,仅验证 H2 `MODE=MySQL`。该限制沿用 T2 的未决项。 |
||||
|
|
||||
|
## 阻断项 |
||||
|
|
||||
|
T3 不通过。C-1、I-1、I-2、I-3 均必须在开始 T4 前处理;其中 C-1 和 I-1 优先触发规划/契约 Rewind 或获得明确的来源变更批准。既有 7 项测试通过不能覆盖这些来源和分类语义问题。 |
||||
@ -0,0 +1,130 @@ |
|||||
|
# W2-T4 修复复审报告 |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。本轮仅复审修复提交 `52751e5`,对照第一轮报告 `W2-T4.md`,未修改任何实现、测试、迁移或配置文件;仅新增本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
第一轮阻断项 `W2-T4-I1` 已关闭。两个投影查询均已保证技能分类的 `occupation_id` 与职位技能关系的 `occupation_id` 一致,新增测试真实构造了跨职位分类错配数据并验证查询排除该数据。规定回归集全部通过,未发现新的 Critical、Important 或阻断项。 |
||||
|
|
||||
|
## Finding 关闭状态 |
||||
|
|
||||
|
### W2-T4-I1:分类过滤查询未强制 category 与 occupation 属于同一职位 |
||||
|
|
||||
|
状态:**已关闭**。 |
||||
|
|
||||
|
关闭证据: |
||||
|
|
||||
|
- 整职位查询在分类关联后增加了 `c.occupationId = os.occupationId`: |
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:36-40` |
||||
|
- 职位加分类查询同样增加了该约束,并保留 `os.occupationId = :occupationId` 与 `os.categoryId = :categoryId`: |
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:63-69` |
||||
|
- 两个查询均继续通过 `os.skillId = s.id` 和 `os.categoryId = c.id` 连接技能及分类,未改变投影字段或原过滤条件。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Java:OpenJDK 17.0.16 |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
SkillCloudSeedDataIntegrationTest: Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyRepositoryTest: Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
Total: Tests run: 16, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 40.769 s |
||||
|
``` |
||||
|
|
||||
|
## 必须检查项逐项结论 |
||||
|
|
||||
|
### 1. 两个投影查询均约束 category 所属 occupation,且保留过滤:通过 |
||||
|
|
||||
|
两个查询都在连接 `os.categoryId = c.id` 后增加: |
||||
|
|
||||
|
```sql |
||||
|
AND c.occupationId = os.occupationId |
||||
|
``` |
||||
|
|
||||
|
整职位查询继续只按目标 `occupationId` 过滤;职位加分类查询继续同时按目标 `occupationId` 和 `categoryId` 过滤。因此: |
||||
|
|
||||
|
- 不会再通过职位 A 的关系读取仅属于职位 B 的分类; |
||||
|
- 分类参数仍必须与关系中的分类 ID 完全相等; |
||||
|
- 职位参数语义未改变。 |
||||
|
|
||||
|
位置: |
||||
|
|
||||
|
- `OccupationSkillRepository.java:36-43` |
||||
|
- `OccupationSkillRepository.java:63-72` |
||||
|
|
||||
|
### 2. 新增测试真实插入跨职位错配关系,且可证伪修复:通过 |
||||
|
|
||||
|
新增测试位于 `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyRepositoryTest.java:123-152`。 |
||||
|
|
||||
|
测试通过 `JdbcTemplate` 向真实 `occupation_skills` 表插入: |
||||
|
|
||||
|
- `occupation_id = 1` |
||||
|
- `category_id = 201` |
||||
|
- `skill_id = 1` |
||||
|
- `source_version_id = 2` |
||||
|
|
||||
|
其中分类 `201` 在 V36 中明确属于职位 `2`,见 `V36__skill_cloud_seed.sql:67`;技能 `1` 存在,见 `V36__skill_cloud_seed.sql:129`。插入使用合法外键值和完整来源字段,因此构造的是数据库允许保存、但职位与分类不属于同一节点的逻辑错配关系。 |
||||
|
|
||||
|
该测试没有断言 SQL 文本,而是调用真实 Repository 方法并检查返回结果。修复前的查询缺少同职位约束,插入关系会满足 `os.skillId = s.id`、`os.categoryId = c.id` 和 `os.occupationId = 1`,因此: |
||||
|
|
||||
|
- 整职位查询会包含技能 `1`,在第 `142` 行 `doesNotContain(1L)` 失败; |
||||
|
- `occupation=1, category=201` 查询会包含该关系,在第 `144-146` 行失败。 |
||||
|
|
||||
|
修复后该关系被 `c.occupationId = os.occupationId` 排除,测试通过,可证伪性成立。 |
||||
|
|
||||
|
测试方法带 `@Transactional`,见 `SkillTaxonomyRepositoryTest.java:123-124`,插入数据在测试结束后回滚,不污染其他测试或 V36 计数。 |
||||
|
|
||||
|
### 3. 三项断言均正确覆盖:通过 |
||||
|
|
||||
|
`SkillTaxonomyRepositoryTest.java:136-151` 分别验证: |
||||
|
|
||||
|
- 整职位查询仍包含正常技能 `3`,且不包含错配技能 `1`;当前断言在 `139-142` 行。 |
||||
|
- 错误分类组合 `occupation=1, category=201` 返回空列表;当前断言在 `144-146` 行。 |
||||
|
- 正确组合 `occupation=2, category=201` 仍返回技能 `1`,证明修复没有误伤合法数据;当前断言在 `148-151` 行。 |
||||
|
|
||||
|
三项断言覆盖了第一轮要求的安全边界和正常路径。 |
||||
|
|
||||
|
### 4. 正常查询、空 Importance、来源字段和计数:通过 |
||||
|
|
||||
|
- 原整职位和分类过滤测试继续验证职位 `1` 返回 15 项、分类 `101` 返回精确技能集合,以及错误职位分类返回空:`SkillTaxonomyRepositoryTest.java:95-120`。 |
||||
|
- O*NET 来源字段、`importance`、`importanceSourceCode`、`importanceElementId`、代理标记和排序字段的原断言仍通过:`SkillTaxonomyRepositoryTest.java:154-188`。 |
||||
|
- Figma 的空 Importance 场景仍通过:`SkillTaxonomyRepositoryTest.java:176-188`。 |
||||
|
- V36 计数仍为来源版本 2、行业 3、职位 10、分类 68、技能 72、别名 72、职位技能关系 139:`SkillTaxonomyRepositoryTest.java:72-81`。 |
||||
|
- 指定回归集 16 项全部通过,未观察到由新增约束造成的正常查询、投影字段或种子数据回归。 |
||||
|
|
||||
|
### 5. 提交范围与 T5 边界:通过 |
||||
|
|
||||
|
`git diff --name-only 0c77824..52751e5` 仅列出: |
||||
|
|
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/skill/repository/OccupationSkillRepository.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillTaxonomyRepositoryTest.java` |
||||
|
|
||||
|
未修改 V35/V36、生产配置、安全配置或其他模块,也未实现任何 T5 排名、匹配或学习路径代码。`git diff --check 0c77824..52751e5` 通过。 |
||||
|
|
||||
|
## 剩余阻断项 |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
## 非阻断备注 |
||||
|
|
||||
|
V35 仍允许数据库层面写入 `occupation_id` 与 `category_id` 逻辑不一致的关系,因为当前外键是单列外键;本次修复保证 Repository 查询不会返回此类错配数据。是否需要增加数据库复合完整性约束属于独立的数据模型变更,不影响 T4 通过。 |
||||
@ -0,0 +1,150 @@ |
|||||
|
# W2-T4 独立评审报告 |
||||
|
|
||||
|
## 状态 |
||||
|
|
||||
|
完成。本轮只评审提交 `0c77824`、修订后的契约与规划产物、V35/V36,并独立执行指定测试;未修改实现、测试、迁移或配置文件,仅写入本报告。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**FAIL** |
||||
|
|
||||
|
未发现 Critical。发现 1 项 Important:技能投影查询没有保证 `occupation_skills.category_id` 对应的分类属于同一个 `occupation_id`,在数据库出现逻辑错配关系时会返回跨职位数据。该问题会阻断 T4 通过,修复并复审后可进入 T5。 |
||||
|
|
||||
|
## 独立测试输出 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Java:OpenJDK 17.0.16 |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
SkillCloudSeedDataIntegrationTest: Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyMigrationTest: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
SkillTaxonomyRepositoryTest: Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
Total: Tests run: 15, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 44.358 s |
||||
|
``` |
||||
|
|
||||
|
## 必须检查项逐项结论 |
||||
|
|
||||
|
### 1. 实体与 V35 映射:通过 |
||||
|
|
||||
|
7 个实体均映射到 V35 对应表,主键为 `Long` + `GenerationType.IDENTITY`。已逐表核对表名、列名、长度、可空性和类型: |
||||
|
|
||||
|
- `SkillSourceVersion` 与 `skill_source_versions`:字段、长度和可空性一致。 |
||||
|
- `SkillIndustry` 与 `skill_industries`:字段、长度和可空性一致。 |
||||
|
- `SkillOccupation` 与 `skill_occupations`:字段、长度和可空性一致。 |
||||
|
- `SkillCategory` 与 `skill_categories`:字段、长度和可空性一致。 |
||||
|
- `Skill` 与 `skills`:字段、长度和可空性一致。 |
||||
|
- `SkillAlias` 与 `skill_aliases`:字段、长度和可空性一致;该表无 `updated_at`,实体也未映射该列。 |
||||
|
- `OccupationSkill` 与 `occupation_skills`:`DECIMAL(4,2)`、来源字段长度、`TINYINT(1)` 代理标记及可空性匹配;`importanceIsProxy` 使用 `@JdbcTypeCode(SqlTypes.TINYINT)`。 |
||||
|
|
||||
|
本次测试在 `ddl-auto=validate` 下执行通过,证明 V35 七张表实际参与了 Hibernate 校验。 |
||||
|
|
||||
|
### 2. Schema Filter 与生产配置:通过 |
||||
|
|
||||
|
`SkillTaxonomyRepositoryTest` 的 validate filter 明确包含全部七个 V35 表: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyRepositoryTest.java:182-231` |
||||
|
|
||||
|
因此该 filter 隔离的是仓库历史中与本任务无关的表,不会跳过 V35 实体的列类型或可空性校验。测试通过 `@TestPropertySource` 覆盖测试数据库和 `ddl-auto`,提交未修改生产 `application.yml`、`SecurityConfig` 或其他配置。 |
||||
|
|
||||
|
### 3. 行业、职位、分类关联校验:通过 |
||||
|
|
||||
|
以下 Repository 方法正确表达了单节点关系约束: |
||||
|
|
||||
|
- `SkillOccupationRepository.existsByIdAndIndustryId` / `findByIdAndIndustryId` |
||||
|
- `SkillCategoryRepository.existsByIdAndOccupationId` / `findByIdAndOccupationId` |
||||
|
|
||||
|
测试覆盖了正确组合、跨行业职位、跨职位分类和不存在的 ID: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyRepositoryTest.java:78-88` |
||||
|
|
||||
|
### 4. occupation + 可选 category 查询的同职位约束:不通过 |
||||
|
|
||||
|
两个投影查询都只连接 `os.category_id = c.id`,没有断言 `c.occupation_id` 与 `os.occupation_id` 相同: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:36-40` |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:62-66` |
||||
|
|
||||
|
V35 对 `occupation_skills.category_id` 只有单列外键,不阻止一条关系的 `occupation_id` 指向职位 A、`category_id` 指向职位 B 的分类: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V35__skill_cloud_taxonomy.sql:114-117` |
||||
|
|
||||
|
这违反了契约要求“分类必须属于职位”: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\changes\n03-skill-cloud\execution-contract.md:59` |
||||
|
|
||||
|
也违反了设计中的“category_id 必须属于 occupation_id”: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\changes\n03-skill-cloud\design.md:119` |
||||
|
|
||||
|
现有测试 `findSkillProjectionsByOccupationIdAndCategoryId(2L, 101L)` 只证明当前种子数据中不存在 `职位 2 + 分类 101` 关系,不能发现上述缺列条件;即使插入一条 `occupation_id=2, category_id=101` 的逻辑错配关系,该查询仍会返回它: |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillTaxonomyRepositoryTest.java:113-115` |
||||
|
|
||||
|
修复方向明确:两个查询均增加分类所属职位约束,例如 `AND c.occupationId = os.occupationId`;同时增加可证伪测试,用跨职位分类关系验证查询不会返回数据。 |
||||
|
|
||||
|
### 5. 投影 14 个字段:通过 |
||||
|
|
||||
|
`SkillCloudSkillProjection` 包含 14 个字段,覆盖技能身份、名称、来源、分类、`importance`、`importanceSourceCode`、`importanceElementId`、`importanceIsProxy`、技能排序值和分类排序值。构造表达式与字段顺序一致,`importance` 使用 `BigDecimal`,代理标记使用 `Boolean`。 |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudSkillProjection.java:5-20` |
||||
|
|
||||
|
### 6. 未提前实现 T5–T8:通过 |
||||
|
|
||||
|
提交只新增 7 个实体、1 个投影、7 个 Repository 和专项测试;未新增排名服务、匹配服务、学习路径服务、Controller、安全规则或 OpenAPI 变更。Repository 查询明确未实现最终排序,符合 T5 边界。 |
||||
|
|
||||
|
### 7. 测试数据覆盖与可证伪性:除跨职位查询外通过 |
||||
|
|
||||
|
测试使用了 V36 种子数据,覆盖计数、关联校验、整职位查询、可选分类过滤、O*NET 来源、空 Importance、代理标记、别名和按 ID 查询。查找均为精确断言,不是仅计数。 |
||||
|
|
||||
|
但第 4 项的跨职位分类错配缺少可证伪用例;即使 Repository 查询遗漏同职位条件,当前 15 项测试仍全部通过。 |
||||
|
|
||||
|
### 8. 提交范围与历史迁移:通过 |
||||
|
|
||||
|
提交只包含允许的 16 个文件。独立哈希核对确认 V35/V36 在 `0c77824` 中未发生变化,提交未修改其他模块、已有迁移、生产配置或安全配置。 |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
#### W2-T4-I1:分类过滤查询未强制 category 与 occupation 属于同一职位 |
||||
|
|
||||
|
证据: |
||||
|
|
||||
|
- `OccupationSkillRepository` 两个查询均只有 `os.categoryId = c.id`,缺少 `c.occupationId = os.occupationId`: |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:36-40` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:62-66` |
||||
|
- V35 外键不提供复合 `(occupation_id, category_id)` 完整性: |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\resources\db\migration\V35__skill_cloud_taxonomy.sql:114-117` |
||||
|
- 契约明确要求分类必须属于职位: |
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\execution-contract.md:59` |
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\design.md:119` |
||||
|
|
||||
|
影响:一旦数据写入出现逻辑错配,整职位查询会返回其他职位的分类技能;带分类查询也可能在对应关系存在时返回跨职位数据,破坏词云筛选边界,并影响后续匹配和学习路径。当前种子数据恰好一致,因此测试未暴露问题。 |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
无额外阻断项。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
无。修复语义明确:查询必须同时绑定 category 所属 occupation;修复后应增加一条跨职位分类关系的失败用例并重新评审。 |
||||
@ -0,0 +1,94 @@ |
|||||
|
# W2-T5 聚焦修复独立复审报告 |
||||
|
|
||||
|
- **评审对象**:T5 I-1 聚焦修复工作区差异(基于 `e3eecfe`) |
||||
|
- **前次评审**:`changes/n03-skill-cloud/.superpowers/sdd/reviews/W2-T5.md`,结论 `FAIL` |
||||
|
- **复审范围**:仅检查 `SkillCloudItem.java`、`SkillCloudQueryService.java`、`SkillCloudQueryServiceTest.java` 及其对既有行为的影响 |
||||
|
- **结论**:**PASS** |
||||
|
- **剩余阻断项**:无 |
||||
|
|
||||
|
## Finding 关闭状态 |
||||
|
|
||||
|
### [I-1] 响应项遗漏技能来源标识:已关闭 |
||||
|
|
||||
|
修复已同时补齐 DTO 字段和 Service 映射: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudItem.java:11-12` |
||||
|
- 新增 `sourceType` |
||||
|
- 新增 `sourceCode` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\SkillCloudQueryService.java:102-117` |
||||
|
- `toItem(...)` 在 `nameEn` 后依次映射 `projection.sourceType()` 和 `projection.sourceCode()`(第 109-110 行) |
||||
|
- 热度来源仍独立映射为 `heatSourceCode`、`heatElementId`、`heatProxy`(第 114-116 行) |
||||
|
|
||||
|
字段语义未混淆:技能自身来源是 `sourceType/sourceCode`,O*NET Importance 来源是 `heatSourceCode/heatElementId/heatProxy`。 |
||||
|
|
||||
|
### 双来源测试与可证伪性:已关闭 |
||||
|
|
||||
|
`D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillCloudQueryServiceTest.java:198-236` 同时锁定两类来源: |
||||
|
|
||||
|
- O*NET 技术来源:第 219-220 行精确断言 |
||||
|
- `sourceType = "O*NET"` |
||||
|
- `sourceCode = "15-1254.00|React|2.E.7.h"` |
||||
|
- ESCO 技能来源:第 232-234 行精确断言 |
||||
|
- `sourceType = "ESCO"` |
||||
|
- `sourceCode = "http://data.europa.eu/esco/skill/3cd569a2-4f88-4c1e-9995-8dce8c5e51a7"` |
||||
|
- 来源隔离:第 235 行额外断言 JavaScript 的 `heatSourceCode = "15-1254.00"`,与 ESCO `sourceCode` 明显不同。 |
||||
|
|
||||
|
因此: |
||||
|
|
||||
|
- 若 `sourceType` 或 `sourceCode` 映射被遗漏并传为 `null`,上述精确断言会失败。 |
||||
|
- 若二者被交换,O*NET 与 ESCO 的精确值断言会失败。 |
||||
|
- 若技能来源与热度来源混用,React 的完整 O*NET 三元组断言及 JavaScript 的 URI/SOC 对照断言会失败。 |
||||
|
|
||||
|
## 原有行为回归 |
||||
|
|
||||
|
独立执行指定环境与命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 24, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
Surefire 分类计数: |
||||
|
|
||||
|
- `SkillTaxonomyMigrationTest`:2 |
||||
|
- `SkillCloudSeedDataIntegrationTest`:8 |
||||
|
- `SkillTaxonomyRepositoryTest`:6 |
||||
|
- `SkillCloudQueryServiceTest`:8 |
||||
|
|
||||
|
已核验原有行为: |
||||
|
|
||||
|
- 全量查询、连续排名:`SkillCloudQueryServiceTest.java:62-81` |
||||
|
- 热度降序、同热度名称排序、null heat 末位:`SkillCloudQueryServiceTest.java:84-107` |
||||
|
- 同值排序的 `skillId` 稳定性:`SkillCloudQueryServiceTest.java:109-145` |
||||
|
- 分类过滤及缺省分类:`SkillCloudQueryServiceTest.java:166-179` |
||||
|
- 合法空分类返回空列表:`SkillCloudQueryServiceTest.java:181-196` |
||||
|
- 必填/正整数/未知节点/跨行业职位/跨职位分类校验:`SkillCloudQueryServiceTest.java:238-264` |
||||
|
- provenance 字段映射:`SkillCloudQueryServiceTest.java:198-236` |
||||
|
|
||||
|
本次修复没有改变排序、过滤、校验或热度来源映射逻辑,仅扩展响应字段与测试断言。 |
||||
|
|
||||
|
## 变更范围 |
||||
|
|
||||
|
自 `e3eecfe` 起,T5 实现差异仅包含: |
||||
|
|
||||
|
- `SkillCloudQueryService.java` |
||||
|
- `SkillCloudItem.java` |
||||
|
- `SkillCloudQueryServiceTest.java` |
||||
|
|
||||
|
共 `20 insertions, 3 deletions`。未修改 V35/V36、Repository、安全配置或应用配置;未发现 T6/T7/T8 的 Controller、匹配度 Service、学习路径 Service 或权限配置实现。 |
||||
|
|
||||
|
## 非阻断备注 |
||||
|
|
||||
|
- **M-1 仍为 Minor**:设计文档中的 `heatSource` 嵌套形态与当前三个扁平热度来源字段尚未统一,按前次评审要求留待 T8 明确 OpenAPI 和接口文档。 |
||||
|
- **M-2 仍为 Minor**:Service 层未单独断言无 heat 技能的 `sourceType/sourceCode`;Repository 层已在该场景断言 Figma 保留来源(`SkillTaxonomyRepositoryTest.java:183-188`),且 Service 映射无条件执行,不构成阻断。 |
||||
|
|
||||
|
## 最终裁决 |
||||
|
|
||||
|
`PASS`。I-1 已关闭,指定联合回归全部通过,未发现新的 Critical 或 Important 问题;T5 可进入后续任务门禁。 |
||||
@ -0,0 +1,94 @@ |
|||||
|
# W2-T5 独立评审报告 |
||||
|
|
||||
|
- **评审对象**:`e3eecfe feat: add skill cloud query service` |
||||
|
- **评审基线**:`52751e5`(T4 复审通过提交) |
||||
|
- **结论**:**FAIL** |
||||
|
- **评审范围**:仅检查提交 `e3eecfe` 的 5 个文件;未修改实现文件。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`。 |
||||
|
|
||||
|
执行: |
||||
|
|
||||
|
```powershell |
||||
|
mvn -Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 24, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
测试数量拆分为:迁移 2、种子数据 8、Repository 6、词云 Service 8。 |
||||
|
|
||||
|
## Important |
||||
|
|
||||
|
### [I-1] 响应项遗漏技能自身的来源标识,违反 T5 的“来源字段”和可追溯要求 |
||||
|
|
||||
|
`SkillCloudSkillProjection` 已查询并携带技能来源字段: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudSkillProjection.java:10-11` |
||||
|
- `sourceType` |
||||
|
- `sourceCode` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:25-26` |
||||
|
- 查询投影包含 `s.sourceType` 和 `s.sourceCode` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\repository\OccupationSkillRepository.java:52-53` |
||||
|
- 分类查询同样包含这两个来源字段 |
||||
|
|
||||
|
但 Service 转换成响应 DTO 时丢弃了这两个字段: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\SkillCloudQueryService.java:102-115` |
||||
|
- `toItem(...)` 没有传入 `projection.sourceType()` 或 `projection.sourceCode()` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudItem.java:5-17` |
||||
|
- Item 只有 `heatSourceCode`、`heatElementId`、`heatProxy`,没有技能来源类型或技能来源编码 |
||||
|
|
||||
|
这会使返回结果无法在接口层区分某个技能来自 ESCO 还是 O*NET `software_skills`,只剩通过数据库内部 ID 才能反查来源。T5 明确要求返回“来源字段”: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\tasks.md:21` |
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\execution-contract.md:41` |
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\specs\skill-cloud\spec.md:86-93` |
||||
|
|
||||
|
当前测试也没有覆盖该缺口:`SkillCloudQueryServiceTest` 只断言了 React 的 `heatSourceCode`、`heatElementId` 和 `heatProxy`(`SkillCloudQueryServiceTest.java:198-223`),没有断言 Item 中应携带 `sourceType`/`sourceCode`。因此该遗漏是可复现但不可被现有测试发现的。 |
||||
|
|
||||
|
**修复要求**:在 `SkillCloudItem` 中补齐技能来源字段,在 `toItem` 中映射,并增加至少一个 ESCO 技能和一个 O*NET `software_skills` 技能的精确断言;同时确认无 heat 的技能仍保留其技能来源。 |
||||
|
|
||||
|
## Minor |
||||
|
|
||||
|
### [M-1] `heatSource` 的响应形态与设计文档不一致,需在 T8 前统一 |
||||
|
|
||||
|
设计文档将词云项描述为包含 `heatSource`: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\changes\n03-skill-cloud\design.md:131` |
||||
|
|
||||
|
当前实现使用三个扁平字段表达同一语义: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\dto\SkillCloudItem.java:13-16` |
||||
|
- `heatSourceCode` |
||||
|
- `heatElementId` |
||||
|
- `heatProxy` |
||||
|
|
||||
|
这不是当前 T5 的运行时错误,但 T8 必须明确 OpenAPI 和接口文档采用扁平字段还是嵌套 `heatSource` 对象,避免契约漂移。 |
||||
|
|
||||
|
### [M-2] T5 服务测试对 null heat 的来源字段覆盖不足 |
||||
|
|
||||
|
`SkillCloudQueryServiceTest` 对 React 的非空 heat 来源做了精确断言,但没有对无 heat 技能(如当前种子数据中的 Figma)断言 `heatSourceCode`、`heatElementId` 和 `heatProxy` 的最终 DTO 值。T4 投影测试覆盖了该场景,因此暂不阻断;修复 I-1 时应顺带补齐服务层断言。 |
||||
|
|
||||
|
## 已通过检查 |
||||
|
|
||||
|
- 行业存在性、职位存在性、行业—职位归属校验均使用 `BusinessException(ErrorCode.BAD_REQUEST)`。 |
||||
|
- 非法 `industryId`、`occupationId`、`categoryId`(null、0、负数)均返回 400 语义;未知节点和跨行业/跨职位节点同样返回 400。 |
||||
|
- `categoryId` 缺省时调用整职位查询;传入时调用职位+分类查询,并保留 Repository 的职位—分类归属约束。 |
||||
|
- 排序实现为 `heat DESC`、null heat 最后、`nameZh ASC`、`skillId ASC`;当前种子数据测试能捕获名称比较器和 skillId 比较器写错,也覆盖了 null heat 末位和连续 rank。 |
||||
|
- 空分类返回成功、`total=0`、空 items。 |
||||
|
- `SkillCloudResponse` 对列表执行 `List.copyOf`,响应列表不可变。 |
||||
|
- 提交范围仅包含 T5 的 Service、3 个 DTO 和测试文件,没有实现 T6/T7/T8,也没有修改 V35/V36、安全配置或既有接口。 |
||||
|
- 未发现事务、空指针、可变集合、随机性或明显性能问题;当前静态词库规模下三次校验查询加一次投影查询是合理的。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
1. 技能来源字段的最终 JSON 名称应为 `sourceType`/`sourceCode`,还是采用带 `skillSource` 前缀的名称;需在修复 I-1 和 T8 OpenAPI 时统一。 |
||||
|
2. `heatSource` 是否需要改成嵌套对象;若保持扁平字段,应同步修订设计文档和后续接口文档。 |
||||
|
3. I-1 修复后必须重新执行本报告使用的 24 项联合回归,并重新评审;T5 评审通过前不得进入 T6。 |
||||
@ -0,0 +1,98 @@ |
|||||
|
# W2-T6 测试隔离修复独立复审报告 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
T6 原 Review 中唯一 Important finding 已关闭。新增的三个隔离测试均通过服务返回结果进行断言,能够分别证伪 `nameZh` 分支、alias 匹配链路和 alias 归属隔离的相关回归;类级事务确保测试数据回滚。未发现新的阻断项,T6 可进入下一任务。 |
||||
|
|
||||
|
## Finding 关闭状态 |
||||
|
|
||||
|
### T6-I1:`nameZh` 与 alias 匹配遗漏无法证伪 |
||||
|
|
||||
|
**状态:已关闭。** |
||||
|
|
||||
|
1. **nameZh-only 隔离成立** |
||||
|
- 测试:`matchesOnlyNameZhForV36JavaWhenOtherIdentityFieldsDoNotMatch` |
||||
|
- 位置:`D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillMatchServiceTest.java:237-252` |
||||
|
- 第 239 行先将 V36 Java 的 `code` 改为 `JAVA_NAME_ZH_ISOLATED`。 |
||||
|
- Java 的 V36 `nameZh` 为 `Java`,`nameEn` 为 `Java (computer programming)`,alias 为 `Java (computer programming)`;学生输入仅为 `Java`。 |
||||
|
- 因此学生输入只能匹配 `nameZh`。删除生产代码的 `nameZh` 分支后,Java 必然进入 `missingSkills`,方法第 246-251 行会失败。 |
||||
|
|
||||
|
2. **alias-only 隔离成立** |
||||
|
- 测试:`matchesOnlyInsertedAliasForV36JavaWhenAliasIsTheOnlyIdentityMatch` |
||||
|
- 位置:`SkillMatchServiceTest.java:254-272` |
||||
|
- 第 256-259 行插入 Java 专属 alias `ONLY.ALIAS-JVM-XYZ`,与目标技能的 `nameZh`、`nameEn`、`skillCode` 均不相同。 |
||||
|
- 学生输入只有该 alias,并直接断言服务返回的 `matchedSkills` 包含 Java、`missingSkills` 包含 Python。 |
||||
|
- 删除 alias 查询或 alias 匹配分支后,Java 不会命中,测试必然失败。 |
||||
|
|
||||
|
3. **alias 归属隔离成立** |
||||
|
- 测试:`doesNotApplyAliasOwnedByAnotherTopSkillToV36Java` |
||||
|
- 位置:`SkillMatchServiceTest.java:274-292` |
||||
|
- 第 276-279 行将唯一 alias 关联到技能 2(Python),学生只输入该 alias。 |
||||
|
- 断言要求技能 2 命中、技能 1 缺失;若 alias 被错误共享给其他 TOP 技能,技能 1 也会命中,分数的断言会失败。 |
||||
|
|
||||
|
### 事务与回滚 |
||||
|
|
||||
|
**状态:满足要求。** |
||||
|
|
||||
|
- `SkillMatchServiceTest` 在类级使用 `@Transactional`:`SkillMatchServiceTest.java:51-52`。 |
||||
|
- 未发现 `@Commit` 或 `@Rollback(false)`。 |
||||
|
- 三个新增测试的 JDBC 插入/更新和 `service.match(...)` 调用均在测试事务内执行,测试结束后由 Spring Test 默认回滚,不会污染共享 H2 或 V36 数据。 |
||||
|
|
||||
|
### 服务结果断言 |
||||
|
|
||||
|
**状态:满足要求。** |
||||
|
|
||||
|
- 三个新增测试均直接对 `SkillMatchResponse` 的 `matchScore()`、`matchedSkills()` 和 `missingSkills()` 做断言,不是仅验证 helper。 |
||||
|
- alias-only 测试还会在第 265-271 行同时校验命中与缺失列表,能够发现“匹配分数正确但技能集合错误”的实现偏差。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
|
||||
|
执行命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 40, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 47.828 s |
||||
|
``` |
||||
|
|
||||
|
测试组成: |
||||
|
|
||||
|
| 测试类 | 数量 | Failures | Errors | |
||||
|
|---|---:|---:|---:| |
||||
|
| `SkillTaxonomyMigrationTest` | 2 | 0 | 0 | |
||||
|
| `SkillCloudSeedDataIntegrationTest` | 8 | 0 | 0 | |
||||
|
| `SkillTaxonomyRepositoryTest` | 6 | 0 | 0 | |
||||
|
| `SkillCloudQueryServiceTest` | 8 | 0 | 0 | |
||||
|
| `SkillMatchServiceTest` | 16 | 0 | 0 | |
||||
|
|
||||
|
## 回归与范围核验 |
||||
|
|
||||
|
- T6 原有 13 项行为测试全部保留,新增 3 项隔离测试,专项测试总数为 16。 |
||||
|
- `nameEn`、`skillCode`、去重、TOP10、分母、非法 JSON、空数据、排序和跨职位隔离等既有测试均通过。 |
||||
|
- `git diff --name-only -- mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillMatchService.java mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillNameNormalizer.java` 无输出,生产实现相对当前审阅基线未被本次测试修复修改。 |
||||
|
- 源码搜索未发现 `LearningPath`、`SkillCloudController`、`SkillMatchController` 或相关接口实现;T7/T8 未实现。 |
||||
|
- `git diff --check` 未报告代码差异错误;输出中的无关 `.trae` 文档警告不属于 T6 范围。 |
||||
|
|
||||
|
## 剩余阻断项 |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
## 结论 |
||||
|
|
||||
|
T6-I1 已关闭,T6 测试隔离修复满足可证伪性、事务隔离和服务结果断言要求。允许 T6 进入后续流程,但在流程门禁正式记录 `PASS` 前不得启动 T7。 |
||||
@ -0,0 +1,71 @@ |
|||||
|
# W2-T6 独立评审报告 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**FAIL** |
||||
|
|
||||
|
T6 生产实现的主流程与当前契约基本一致,但测试未满足本次评审明确要求的可证伪性,尤其无法发现 `nameZh` 匹配或别名匹配被删除的回归。必须先补齐隔离型测试并通过复审,才能进入 T7。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
- `JAVA_HOME=D:\java\jdk` |
||||
|
- Maven:`D:\maven\apache-maven-3.9.7\bin\mvn.cmd` |
||||
|
- 工作目录:`D:\1\zhi\zhilai\mydreamjob-server` |
||||
|
|
||||
|
命令: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 37, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 58.642 s |
||||
|
``` |
||||
|
|
||||
|
测试组成:迁移 2、种子数据 8、Repository 6、词云查询 8、个人匹配 13。 |
||||
|
|
||||
|
## 阻断项 |
||||
|
|
||||
|
### Important:无法证伪 `nameZh` 与 alias 匹配遗漏 |
||||
|
|
||||
|
文件: |
||||
|
|
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\test\java\com\mydreamjob\skill\SkillMatchServiceTest.java:212-235` |
||||
|
- `D:\1\zhi\zhilai\mydreamjob-server\src\main\java\com\mydreamjob\skill\SkillMatchService.java:137-142` |
||||
|
|
||||
|
问题: |
||||
|
|
||||
|
- 测试方法 `matchesNameZhNameEnSkillCodeAndAliasAndDeduplicatesStudentSkills` 试图一次覆盖 `nameZh`、`nameEn`、`skillCode` 和别名,但没有隔离各候选来源。 |
||||
|
- 技能 1 的 `nameZh` 为“数据库”,同时注册别名 `JVM Language`。学生技能数组同时包含“数据库”和 `jvm-language`。 |
||||
|
- 如果删除 `SkillMatchService.matches(...)` 中的 `nameZh` 判断,技能 1 仍可通过别名命中,测试继续通过。 |
||||
|
- 如果删除 alias 查询、alias 分支或整个别名匹配链路,技能 1 仍可通过 `nameZh` 命中,测试同样继续通过。 |
||||
|
- 因此现有测试不能证明 REQ-SM-3 和 TO-5 中“标准名称 + 别名”匹配能力存在;这是本次评审明确要求验证的回归保护,不能仅依赖实现代码的静态正确性。 |
||||
|
|
||||
|
要求的修复方向: |
||||
|
|
||||
|
- 增加独立技能/独立场景,确保某个标准技能只能通过 `nameZh` 命中,另一个只能通过 alias 命中。 |
||||
|
- 保留现有 `nameEn` 和 `skillCode` 隔离断言。 |
||||
|
- 建议再断言 alias 必须归属于目标 `skillId`,避免别名被错误应用于其他标准技能时测试仍通过。 |
||||
|
|
||||
|
## 已核验且未发现阻断的行为 |
||||
|
|
||||
|
- 匹配集合直接对 `SkillCloudQueryService.query(...)` 的结果取 `subList(0, min(10, size))`,服务内未重新排序。 |
||||
|
- 无 profile、空白 JSON、空数组和语法损坏 JSON 均按无技能处理;损坏 JSON 测试验证不抛 500 且日志不含原始 JSON。 |
||||
|
- 归一化覆盖 NFKC、trim、`Locale.ROOT` 小写、Unicode 连字符、空白折叠、句点移除,并保留 `+`、`#`。 |
||||
|
- `nameEn` 和 `skillCode` 在现有测试中有可区分断言;归一化后的学生技能使用 `Set` 去重。 |
||||
|
- 分数使用 `Math.round(matched * 100.0 / actualTopSize)`,`actualTopSize=0` 返回 0,`1/3=33`,少于十条和恰好十条均有断言。 |
||||
|
- 命中/缺失列表按 TOP 顺序构建,并无额外排序;无匹配场景的并集顺序与词云 TOP10 对齐。 |
||||
|
- 响应 DTO 只包含技能投影,不包含原始 `skills_json`、手机号、邮箱或简历内容。 |
||||
|
- T6 为只读事务,无持久化;未实现 T7/T8。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
- 无设计或接口语义待裁决。 |
||||
|
- 待修复 T6 测试可证伪性后,必须重新执行同一回归命令并进行独立复审;复审 `PASS` 前不得启动 T7。 |
||||
@ -0,0 +1,80 @@ |
|||||
|
# W2-T7 顺序测试修复复审报告 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
- 复审对象:`mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillLearningPathServiceTest.java` |
||||
|
- 对照报告:`changes/n03-skill-cloud/.superpowers/sdd/reviews/W2-T7.md` |
||||
|
- 生产服务基线:`fca1160 feat: add skill learning path service` |
||||
|
- 结论:T7-I1 已关闭;当前顺序测试可证伪,原有 T7 行为测试未回归,未发现 T8 实现。 |
||||
|
|
||||
|
## Finding 关闭状态 |
||||
|
|
||||
|
### T7-I1:测试无法阻止学习路径按名称重排 |
||||
|
|
||||
|
**已关闭。** |
||||
|
|
||||
|
1. Mockito 场景的输入名称和 ID 均不等于自然升序: |
||||
|
- `SkillLearningPathServiceTest.java:135-139` |
||||
|
- 名称输入为 `Skill Z -> Skill A -> Skill M`,自然升序为 `Skill A -> Skill M -> Skill Z`。 |
||||
|
- ID 输入为 `30 -> 10 -> 20`,自然升序为 `10 -> 20 -> 30`。 |
||||
|
- `SkillLearningPathServiceTest.java:155-156` 显式断言输入名称、ID 都不等于自然排序结果。 |
||||
|
- `SkillLearningPathServiceTest.java:157-165` 读取服务返回对象,并严格断言返回 ID 为 `30, 10, 20`、名称为 `Skill Z, Skill A, Skill M`。 |
||||
|
- 因此,生产实现若按名称或 ID 重排,现有测试必然失败。 |
||||
|
|
||||
|
2. 断言读取的是服务返回结果,而不是只验证 Mockito 输入: |
||||
|
- `SkillLearningPathServiceTest.java:157-165` 使用 `response.items()` 的 `skillId` 和 `skillName` 做精确顺序断言。 |
||||
|
- `SkillLearningPathServiceTest.java:160-162` 同时断言优先级严格为 `1, 2, 3`。 |
||||
|
- 满足“返回顺序、priority 和技能 ID”均被验证的要求。 |
||||
|
|
||||
|
3. 新增 V36 真实数据场景覆盖不同热度分组和名称顺序: |
||||
|
- `SkillLearningPathServiceTest.java:89-121` |
||||
|
- 使用教育行业 `3`、教学设计职位 `9` 调用真实 `SkillMatchService` 和 `SkillLearningPathService`。 |
||||
|
- `SkillLearningPathServiceTest.java:102-110` 断言缺失技能共 10 项、存在多个不同热度值,且名称和 ID 顺序均不等于自然升序。 |
||||
|
- `SkillLearningPathServiceTest.java:112-120` 将服务返回的 `skillId`、`skillName` 与 `matchResponse.missingSkills()` 的顺序做严格比较,并断言优先级为 `1..10`。 |
||||
|
- 因此,真实数据场景不是仅验证输入或数量,而是验证了学习路径严格复用匹配结果的缺失技能顺序。 |
||||
|
|
||||
|
4. 原有 T7 行为测试未回归: |
||||
|
- 四类模板测试:`SkillLearningPathServiceTest.java:176-265`,包括编程语言、框架、工具、通用能力、未知/空模板及分类不存在回退。 |
||||
|
- 空路径测试:`SkillLearningPathServiceTest.java:268-280`,断言 `total=0`、空列表且不访问分类仓储。 |
||||
|
- 重复调用测试:`SkillLearningPathServiceTest.java:282-309`,断言顺序、优先级和文本一致。 |
||||
|
- 本次修复仅修改测试文件;`SkillLearningPathService.java` 相对 `fca1160` 的生产差异为空。 |
||||
|
|
||||
|
5. 未发现 T8 实现: |
||||
|
- 当前 HEAD 仍为 `fca1160`。 |
||||
|
- 源码和测试目录未发现 `SkillCloudController`、`SkillLearningPathController`、`/skills` 词云或学习路径接口及对应安全配置变更。 |
||||
|
- 未发现超出 T7 范围的生产实现。 |
||||
|
|
||||
|
## 独立测试结果 |
||||
|
|
||||
|
环境: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' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 50, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
分类统计: |
||||
|
|
||||
|
```text |
||||
|
SkillTaxonomyMigrationTest: 2 passed |
||||
|
SkillCloudSeedDataIntegrationTest: 8 passed |
||||
|
SkillTaxonomyRepositoryTest: 6 passed |
||||
|
SkillCloudQueryServiceTest: 8 passed |
||||
|
SkillMatchServiceTest: 16 passed |
||||
|
SkillLearningPathServiceTest: 10 passed |
||||
|
``` |
||||
|
|
||||
|
## 剩余阻断项 |
||||
|
|
||||
|
无。T7-I1 已关闭,允许进入 T8 评审前置流程。 |
||||
@ -0,0 +1,72 @@ |
|||||
|
# W2-T7 独立评审报告 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**FAIL** |
||||
|
|
||||
|
- 提交:`fca1160 feat: add skill learning path service` |
||||
|
- 评审对象:`SkillLearningPathService`、`SkillLearningPathItem`、`SkillLearningPathResponse`、`SkillLearningPathServiceTest` |
||||
|
- 阻断项:1 项 Important |
||||
|
- 结论:生产实现整体符合 T7 行为契约,但核心顺序行为缺少可证伪的回归测试,不能放行 T8。 |
||||
|
|
||||
|
## 独立测试 |
||||
|
|
||||
|
执行环境: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' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 49, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 1:00 min |
||||
|
``` |
||||
|
|
||||
|
## Important |
||||
|
|
||||
|
### T7-I1:测试无法阻止学习路径按名称重排 |
||||
|
|
||||
|
- 文件:`mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillLearningPathServiceTest.java:98-117` |
||||
|
- 相关实现:`mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillLearningPathService.java:35-38` |
||||
|
|
||||
|
现有单测的 `missing` 顺序为: |
||||
|
|
||||
|
- `First` / `skillId=31` / `heat=1.00` |
||||
|
- `Second` / `skillId=32` / `heat=10.00` |
||||
|
- `Third` / `skillId=33` / `heat=5.00` |
||||
|
|
||||
|
期望输出保持该输入顺序。但输入的名称和 `skillId` 本身均为升序,因此把实现改成: |
||||
|
|
||||
|
```java |
||||
|
missingSkills.stream() |
||||
|
.sorted(Comparator.comparing(SkillCloudItem::nameZh)) |
||||
|
.map(...) |
||||
|
``` |
||||
|
|
||||
|
后,单测仍会通过。真实数据集成测试 `SkillLearningPathServiceTest.java:62-76` 使用职位 1,其当前 TOP10 全部为相同热度,名称顺序也已符合词云次级排序,因此同样无法发现该重排缺陷。 |
||||
|
|
||||
|
T7 的要求是严格复用 `SkillMatchService` 返回的 `missingSkills` 顺序,不自行排序;该回归会破坏 REQ-LP-2。测试需要使用不会被名称排序或 `skillId` 排序偶然保持一致的缺失技能顺序,最好增加多热度分组的真实数据场景。 |
||||
|
|
||||
|
## 已通过检查 |
||||
|
|
||||
|
- 学习路径只遍历 `matchResponse.missingSkills()`,未消费已命中技能;优先级从 1 连续生成。 |
||||
|
- 编程语言、框架、工具、通用四类模板均已实现;未知、空模板编码及分类不存在时回退通用模板。 |
||||
|
- 建议包含技能名,精确断言中未出现 URL、课程链接、讲师、价格、时长、完成度或 AI 内容。 |
||||
|
- 无缺口时返回 `total=0` 和空列表,且不访问分类仓储。 |
||||
|
- 重复调用返回相同顺序、优先级和建议。 |
||||
|
- `SkillLearningPathItem` 仅包含技能 ID、技能名、分类 ID、分类名、优先级和建议,不含隐私字段。 |
||||
|
- 服务使用只读事务;未发现持久化或外部服务调用。 |
||||
|
- 提交只新增 4 个指定文件,未包含 T8 及后续实现。 |
||||
|
|
||||
|
## Minor |
||||
|
|
||||
|
- `SkillLearningPathServiceTest.java:195-221` 覆盖了 null 模板编码、未知模板和分类不存在,但未直接覆盖 `skill.categoryId()==null`;实现已在 `SkillLearningPathService.java:57-58` 回退 GENERAL。 |
||||
|
- 外部内容主要通过精确模板断言和禁用词断言约束;生产类没有外部客户端依赖,因此本项不构成阻断。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
无。修复测试后需重新执行指定回归并复审。 |
||||
@ -0,0 +1,175 @@ |
|||||
|
# N03 SDD W2 整批独立评审报告 |
||||
|
|
||||
|
## 评审对象 |
||||
|
|
||||
|
- 范围:`b960036..f296df4` |
||||
|
- Wave:W2 |
||||
|
- 批次:B2 / T4–T7 |
||||
|
- 提交: |
||||
|
- `0c77824 feat: add skill taxonomy repositories` |
||||
|
- `52751e5 fix: constrain skill projections by occupation category` |
||||
|
- `e3eecfe feat: add skill cloud query service` |
||||
|
- `1666162 fix: expose skill source metadata in cloud items` |
||||
|
- `6eb30e7 feat: add personal skill match service` |
||||
|
- `fca1160 feat: add skill learning path service` |
||||
|
- `f296df4 test: harden learning path ordering regression` |
||||
|
- 评审依据:修订后的 `execution-contract.md`、`design.md`、`tasks.md`、三份 spec、V35/V36,以及 `W2-T4*`、`W2-T5*`、`W2-T6*`、`W2-T7*` 报告。 |
||||
|
- 环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`,工作目录 `D:\1\zhi\zhilai\mydreamjob-server`。 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
W2/T4–T7 的领域与查询实现完整覆盖 B2 范围;词云、个人匹配、学习路径确实共享同一排名管道;未发现 Critical 或 Important 阻断项。W3/T8 可以开始,但必须先处理报告中的 Minor 项,并在 B3 补齐 HTTP、权限、响应序列化、隐私和真实 MySQL 验证。 |
||||
|
|
||||
|
## 测试结果 |
||||
|
|
||||
|
独立执行: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java\jdk' |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest,SkillLearningPathServiceTest' test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 50, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
Surefire 分类: |
||||
|
|
||||
|
| 测试类 | 通过数 | |
||||
|
| --- | ---: | |
||||
|
| `SkillTaxonomyMigrationTest` | 2 | |
||||
|
| `SkillCloudSeedDataIntegrationTest` | 8 | |
||||
|
| `SkillTaxonomyRepositoryTest` | 6 | |
||||
|
| `SkillCloudQueryServiceTest` | 8 | |
||||
|
| `SkillMatchServiceTest` | 16 | |
||||
|
| `SkillLearningPathServiceTest` | 10 | |
||||
|
|
||||
|
补充静态检查: |
||||
|
|
||||
|
- `git diff --check b960036..f296df4` 通过。 |
||||
|
- `git log --oneline b960036..f296df4` 只包含上述 7 个 W2/T4–T7 提交。 |
||||
|
- 范围内实现文件集中在 `mydreamjob-server/src/main/java/com/mydreamjob/skill/**` 与对应测试;未发现 `SkillCloudController`、`SecurityConfig` 或 OpenAPI 变更。 |
||||
|
|
||||
|
## 实现与需求覆盖矩阵 |
||||
|
|
||||
|
| 需求 | W2 结论 | 主要实现与证据 | |
||||
|
| --- | --- | --- | |
||||
|
| REQ-SC-1 | 不属于 W2,待 B3 | 未实现 Controller/SecurityConfig;由 T8 负责。W2 只提供可被 Controller 调用的服务。 | |
||||
|
| REQ-SC-2 | 通过(服务层) | `SkillCloudQueryService.query` 校验必填、正整数、行业—职位关系、分类—职位关系,非法组合抛 `BusinessException(BAD_REQUEST)`,见 `SkillCloudQueryService.java:57-79`、`150-158`;测试见 `SkillCloudQueryServiceTest.java:238-264`。 | |
||||
|
| REQ-SC-3 | 通过 | `heat DESC, nameZh ASC, skillId ASC`,`null` heat 末位,见 `SkillCloudQueryService.java:28-40`、`86-99`、`120-144`;测试覆盖空 heat、同 heat、同名称 skillId,见 `SkillCloudQueryServiceTest.java:84-145`。 | |
||||
|
| REQ-SC-4 | 通过(B2) | 词云返回完整列表;匹配直接取 `min(10, actualSize)`,未另建排序,见 `SkillMatchService.java:53-55`;测试见 `SkillMatchServiceTest.java:85-118`。 | |
||||
|
| REQ-SC-5 | B2 通过,B3 待补 | B2 已返回 rank、技能身份、分类、heat、技能来源及热度来源字段,见 `SkillCloudItem.java:5-19`、`SkillCloudQueryService.java:102-117`;`ApiResponse` 包装属于 T8。 | |
||||
|
| REQ-SM-1 | 通过 | 匹配服务复用 `SkillCloudQueryService.query` 的结果前 `min(10, actualSize)` 项,见 `SkillMatchService.java:53-55`;测试见 `SkillMatchServiceTest.java:85-137`。 | |
||||
|
| REQ-SM-2 | B2 通过,B3 待补 | 服务只通过 `studentUserId` 查询 `student_profiles.skills_json`,缺失、空、损坏 JSON 均按空技能处理,见 `SkillMatchService.java:81-116`;JWT 身份提取必须由 T8 Controller 完成。 | |
||||
|
| REQ-SM-3 | 通过 | NFKC、trim、Locale.ROOT、Unicode 连字符、连续空白、句点、`+`/`#` 规则见 `SkillNameNormalizer.java:12-27`;nameZh/nameEn/skillCode/alias 和别名归属有隔离测试,见 `SkillMatchServiceTest.java:237-292`。 | |
||||
|
| REQ-SM-4 | 通过 | `round(matched * 100.0 / actualTopSize)`,空集合返回 0,重复学生技能通过 `Set` 去重,见 `SkillMatchService.java:54-77`、`100-110`;测试覆盖 1/3=33、全命中、空集合。 | |
||||
|
| REQ-SM-5 | 通过 | 命中/缺失列表按 TOP 顺序分别构造,并集等于实际 TOP10,见 `SkillMatchService.java:59-78`;测试见 `SkillMatchServiceTest.java:294-370`。 | |
||||
|
| REQ-LP-1 | 通过 | 学习路径完全由 `SkillMatchResponse.missingSkills()` 驱动,不重新加入已命中或 TOP10 外技能,见 `SkillLearningPathService.java:29-40`;测试见 `SkillLearningPathServiceTest.java:131-174`。 | |
||||
|
| REQ-LP-2 | 通过 | 直接沿用 missingSkills 顺序,优先级从 1 连续递增,见 `SkillLearningPathService.java:35-38`;真实 V36 多热度分组及名称/ID 乱序测试见 `SkillLearningPathServiceTest.java:88-121`。 | |
||||
|
| REQ-LP-3 | 通过 | 四类确定性模板及未知模板回退见 `SkillLearningPathService.java:56-93`;模板精确断言见 `SkillLearningPathServiceTest.java:176-265`。 | |
||||
|
| REQ-LP-4 | 通过(B2) | 路径项包含技能 ID、名称、分类、优先级和建议,建议文本不含链接、课程、价格、时长、完成度或 AI 内容,见 `SkillLearningPathItem.java:3-10`、`SkillLearningPathServiceTest.java:358-362`。 | |
||||
|
|
||||
|
## 关键检查结论 |
||||
|
|
||||
|
### 1. 三服务共享同一排名管道:通过 |
||||
|
|
||||
|
`SkillCloudQueryService` 是唯一排序实现: |
||||
|
|
||||
|
- 过滤:`SkillCloudQueryService.java:57-84` |
||||
|
- 排序:`SkillCloudQueryService.java:28-40`、`86-88` |
||||
|
- 排名:`SkillCloudQueryService.java:89-91` |
||||
|
- 来源映射:`SkillCloudQueryService.java:102-117` |
||||
|
|
||||
|
`SkillMatchService` 直接调用词云服务并截取前 `min(10, actualSize)` 项,没有复制排序逻辑,见 `SkillMatchService.java:53-55`。`SkillLearningPathService` 直接消费匹配服务的 `missingSkills`,没有重新排序,见 `SkillLearningPathService.java:33-38`。因此 TOP10、匹配缺口和学习路径的顺序语义一致。 |
||||
|
|
||||
|
### 2. 跨任务接口一致性:通过,存在命名待收口项 |
||||
|
|
||||
|
DTO 字段和顺序语义互相衔接: |
||||
|
|
||||
|
- `SkillCloudItem` 提供词云和匹配共用的完整技能项,见 `SkillCloudItem.java:5-19`。 |
||||
|
- `SkillMatchResponse` 返回 `matchScore`、`matchedCount`、`totalCount`、`matchedSkills`、`missingSkills`,见 `SkillMatchResponse.java:5-15`。 |
||||
|
- `SkillLearningPathResponse` 返回 `total`、`items`;`SkillLearningPathItem` 返回 `priority`、技能、分类和建议,见 `SkillLearningPathResponse.java:5-11`、`SkillLearningPathItem.java:3-10`。 |
||||
|
|
||||
|
Minor M1:设计文档 `design.md:131` 使用概念字段名 `heatSource`,而实现采用扁平字段 `heatSourceCode`、`heatElementId`、`heatProxy`,见 `SkillCloudItem.java:15-18`。这不是 B2 行为错误,但 T8 必须在 OpenAPI、接口文档和前端契约中明确最终 JSON 命名,避免文档与运行时漂移。 |
||||
|
|
||||
|
### 3. 领域边界、事务、空值和异常:通过 |
||||
|
|
||||
|
- 三个业务服务均为 `@Transactional(readOnly = true)`,没有写操作,见三个服务类的类级注解。 |
||||
|
- 分类—职位约束同时存在于服务校验和 Repository 投影查询中;跨职位分类错配测试见 `SkillTaxonomyRepositoryTest.java:123-152`。 |
||||
|
- 空 heat 保留并末位排序:`SkillCloudQueryService.java:120-131`。 |
||||
|
- 空结果:词云返回 `total=0, items=[]`;匹配返回 `matchScore=0`、空命中/缺失;学习路径返回空列表,见对应测试。 |
||||
|
- 损坏 JSON 被捕获并仅记录异常类型和 profile ID,不输出原始 JSON,见 `SkillMatchService.java:91-114`。 |
||||
|
- 学习路径未知/空/缺失模板回退通用模板,见 `SkillLearningPathService.java:56-65`、`84-93`。 |
||||
|
|
||||
|
### 4. 确定性与性能:通过,Minor N+1 |
||||
|
|
||||
|
- 排序使用 `BigDecimal.compareTo`、`nameZh`、`skillId` 全序比较,重复请求结果确定。 |
||||
|
- 每个缺失技能最多一次分类模板查询,构建学习路径时最多 10 次查询,见 `SkillLearningPathService.java:61-64`。 |
||||
|
|
||||
|
Minor M2:这是有界的 N+1。当前任务是代表性小词库、最多 10 个路径项,不构成 W2 阻断;如果后续词库或 TOP 上限扩大,应在 T8/T10 或后续优化中改为一次批量读取模板。 |
||||
|
|
||||
|
### 5. 测试可证伪性:通过,有少量 Minor 缺口 |
||||
|
|
||||
|
已覆盖用户要求的关键反例: |
||||
|
|
||||
|
- 跨职位分类错配:`SkillTaxonomyRepositoryTest.java:123-152`、`SkillMatchServiceTest.java:347-370`。 |
||||
|
- 排序错误:`SkillCloudQueryServiceTest.java:84-145` 覆盖热度、同名/同热度的 skillId 稳定排序。 |
||||
|
- 名称、别名和别名归属遗漏:`SkillMatchServiceTest.java:237-292`。 |
||||
|
- 分母错误:`SkillMatchServiceTest.java:120-137`。 |
||||
|
- 优先级重排:`SkillLearningPathServiceTest.java:88-174`。 |
||||
|
- 模板遗漏:`SkillLearningPathServiceTest.java:176-265`。 |
||||
|
- 空路径:`SkillLearningPathServiceTest.java:268-280`。 |
||||
|
|
||||
|
Minor M3:B2 测试未直接覆盖 `SkillLearningPathService.resolveTemplate` 在 `skill.categoryId()==null` 时的通用模板分支;也未通过完整序列化验证所有响应对象不含原始 `skills_json`。生产 DTO 当前不含隐私字段,且 `SkillLearningPathServiceTest.java:358-362` 已约束建议文本;T8/T9 的 MockMvc/端到端测试必须补齐响应级隐私断言。 |
||||
|
|
||||
|
### 6. W2 未越过 T8 边界:通过 |
||||
|
|
||||
|
`git diff --name-only b960036..f296df4` 未出现: |
||||
|
|
||||
|
- `SkillCloudController` 或任何 `/skills/**` Controller; |
||||
|
- `SecurityConfig` 变更; |
||||
|
- OpenAPI 文件; |
||||
|
- `/api/v1/skills/*` 运行时端点。 |
||||
|
|
||||
|
因此 W2 没有实现 T8 的接口、权限或 OpenAPI 工作,边界符合 `tasks.md:24-25` 的后续安排。 |
||||
|
|
||||
|
## 严重级别与发现 |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
无。 |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
- **M1:设计中的 `heatSource` 与实际扁平来源字段未最终对齐。** |
||||
|
- 位置:`changes/n03-skill-cloud/design.md:131`;`mydreamjob-server/src/main/java/com/mydreamjob/skill/dto/SkillCloudItem.java:15-18`。 |
||||
|
- 处理方向:T8 明确选择扁平 `heatSourceCode`/`heatElementId`/`heatProxy` 或嵌套 `heatSource`,并同步 OpenAPI、接口文档和前端示例。 |
||||
|
|
||||
|
- **M2:学习路径模板查询是有界 N+1。** |
||||
|
- 位置:`mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillLearningPathService.java:61-64`。 |
||||
|
- 处理方向:当前最多 10 次查询,不阻断;若词库或 TOP 上限扩大,应批量读取 `(categoryId, occupationId)` 模板映射。 |
||||
|
|
||||
|
- **M3:B2 测试未直接覆盖空 `categoryId` 模板回退和完整响应序列化隐私断言。** |
||||
|
- 位置:`mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillLearningPathServiceTest.java:240-265`、`358-362`。 |
||||
|
- 处理方向:T8 Controller 测试必须验证三个接口的响应不含 `skills_json`、手机号、邮箱和简历内容;可同时补 `categoryId=null` 的 Service 单测。 |
||||
|
|
||||
|
## 未决问题 |
||||
|
|
||||
|
1. `SkillCloudItem` 热度来源字段的最终 JSON 形态尚未在设计中收口,需在 T8 同时对齐实现、OpenAPI 和前端联调文档。 |
||||
|
2. B2 只验证 H2 `MODE=MySQL` 与 Service 层异常类型;真实 MySQL 迁移、HTTP 401/403/400、JWT 到 `studentUserId` 的绑定和 `ApiResponse` 包装仍属于 B3。 |
||||
|
3. B2 没有执行 `mvn clean verify` 或静态 OpenAPI 校验;这些属于 T9/T10 的退出条件,当前不作为 W2 阻断。 |
||||
|
|
||||
|
## 是否放行 W3 |
||||
|
|
||||
|
**放行。** |
||||
|
|
||||
|
W2 的领域行为、排名管道、个人匹配、学习路径和可证伪测试已达到 B2 完成定义。允许进入 W3/T8;W3 开始前必须在实现 Controller/SecurityConfig/OpenAPI 时处理 M1,并在 T8–T10 验证上述 HTTP、权限、隐私和真实数据库边界。 |
||||
@ -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。 |
||||
@ -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 阻断项,因为它与仓库既有安全配置约定一致。 |
||||
@ -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/发布级验证门禁中完成。 |
||||
|
- 本次评审未修改任何实现或测试源码。 |
||||
@ -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 范围内的文件,避免混入无关变更。 |
||||
@ -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 漂移。 |
||||
@ -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` 还需全绿或形成正式豁免记录。 |
||||
@ -0,0 +1,151 @@ |
|||||
|
# N03 运行时启动缺陷修复独立复审报告 |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
**PASS** |
||||
|
|
||||
|
运行时启动缺陷的本轮修复通过独立复审:实体不再强制 `TINYINT` 映射,V37 将 H2 侧列规范化为 `BOOLEAN`,MySQL 侧保持 `BOOLEAN/TINYINT(1)` 语义;标准 T2–T10 回归和跳过测试的打包均通过。完整 `mvn test` 仍有既有、非本次修复引入的失败,不能据此声称全量回归通过。 |
||||
|
|
||||
|
## 审查对象 |
||||
|
|
||||
|
- `mydreamjob-server/src/main/resources/db/migration/V37__skill_cloud_boolean_type.sql` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/skill/domain/OccupationSkill.java` |
||||
|
- `SkillTaxonomyRepositoryTest.java` |
||||
|
- `SkillTaxonomyMigrationTest.java` |
||||
|
- `SkillCloudSeedDataIntegrationTest.java` |
||||
|
- `SkillCloudQueryServiceTest.java` |
||||
|
- 已有证据日志:`target/skill-cloud-v37-red.log`、`skill-cloud-v37-final-green.log`、`skill-cloud-v37-final-package.log` |
||||
|
|
||||
|
## 根因闭环 |
||||
|
|
||||
|
原始启动失败为: |
||||
|
|
||||
|
```text |
||||
|
Schema-validation: wrong column type encountered in column [importance_is_proxy] |
||||
|
found [bit (Types#BIT)], but expecting [tinyint (Types#TINYINT)] |
||||
|
``` |
||||
|
|
||||
|
闭环判断: |
||||
|
|
||||
|
1. `OccupationSkill.importanceIsProxy` 原先使用显式 `@JdbcTypeCode(SqlTypes.TINYINT)`,把 Hibernate 的 JDBC 期望固定为 `TINYINT`。 |
||||
|
2. 本轮已移除该显式注解及其 import;字段仍是 `Boolean`,列名、可空性和其他字段映射未改变。 |
||||
|
3. 新回归测试同时断言: |
||||
|
- Hibernate 属性返回类型为 `Boolean.class`; |
||||
|
- 字段不再存在显式 `JdbcTypeCode` 注解。 |
||||
|
4. V37 将 `occupation_skills.importance_is_proxy` 规范化为 `BOOLEAN NOT NULL DEFAULT FALSE`,并重建原 CHECK 约束。H2 `MODE=MySQL` 下迁移测试确认元数据为 `BOOLEAN / FALSE / NO`。 |
||||
|
5. MySQL 8.0.43 临时实例实际执行同一 DDL 后,物理元数据为 `tinyint / tinyint(1) / default 0 / NO`,读写结果为默认 `0`、`FALSE=0`、`TRUE=1`。 |
||||
|
|
||||
|
因此根因修复成立:实体不再强制 TINYINT;H2 使用真正的 BOOLEAN;MySQL 保留 BOOLEAN 别名对应的 TINYINT(1) 物理语义。 |
||||
|
|
||||
|
## V35/V36 与 Flyway 安全检查 |
||||
|
|
||||
|
独立比较结果: |
||||
|
|
||||
|
- `V35__skill_cloud_taxonomy.sql`:工作区与 `HEAD` 的 Git hash 均为 `db81f934...`,无差异。 |
||||
|
- `V36__skill_cloud_seed.sql`:工作区与 `HEAD` 的 Git hash 均为 `e4a03a40...`,无差异。 |
||||
|
- V35/V36 均未改写,已应用环境的 Flyway checksum 不受影响。 |
||||
|
- V37 为追加迁移,未修改历史迁移内容。 |
||||
|
|
||||
|
H2 `MODE=MySQL` 兼容性由实际迁移与测试上下文启动验证;MySQL 兼容性由 MySQL 8.0.43 实例实际执行 `DROP CONSTRAINT`、`MODIFY COLUMN BOOLEAN`、`ADD CONSTRAINT` 验证。MySQL 8.4 未在本机安装,未做 8.4 二进制验证;所用 DDL 语法属于 MySQL 8.0/8.4 的通用受支持语法。 |
||||
|
|
||||
|
## 布尔语义、默认值与 NULL 检查 |
||||
|
|
||||
|
V37 的顺序为: |
||||
|
|
||||
|
1. 删除旧的 `ck_occupation_skills_importance_proxy`; |
||||
|
2. 将列改为 `BOOLEAN NOT NULL DEFAULT FALSE`; |
||||
|
3. 重建 `CHECK (importance_is_proxy IN (FALSE, TRUE))`。 |
||||
|
|
||||
|
经验证: |
||||
|
|
||||
|
- 默认值:新行未显式赋值时为 `FALSE/0`。 |
||||
|
- 非空:迁移测试断言 `IS_NULLABLE=NO`,并将 NULL 更新验证为失败。 |
||||
|
- 布尔读写:`FALSE`、`TRUE` 可读写,查询结果与布尔比较均正确。 |
||||
|
- JDBC 断言:查询测试和种子数据测试已兼容 H2 返回 `Boolean` 以及旧驱动可能返回数值两种形式,不会因方言差异误判。 |
||||
|
- `preferred_boolean_jdbc_type=TINYINT`:源代码与配置中未发现该属性;标准测试命令不需要额外 `-D` 参数。 |
||||
|
|
||||
|
## 独立测试与打包结果 |
||||
|
|
||||
|
环境: |
||||
|
|
||||
|
```text |
||||
|
JAVA_HOME=D:\java\jdk |
||||
|
Maven=D:\maven\apache-maven-3.9.7\bin\mvn.cmd |
||||
|
``` |
||||
|
|
||||
|
### T2–T10 指定测试组 |
||||
|
|
||||
|
在 `mydreamjob-server` 下执行,未附加任何布尔类型属性: |
||||
|
|
||||
|
```text |
||||
|
mvn -Dtest=SkillCloudAuthorizationIntegrationTest,SkillCloudControllerTest,SkillCloudEndToEndIntegrationTest,SkillCloudQueryServiceTest,SkillCloudSeedDataIntegrationTest,SkillLearningPathServiceTest,SkillMatchServiceTest,SkillTaxonomyMigrationTest,SkillTaxonomyRepositoryTest test |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 74, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
Total time: 55.561 s |
||||
|
``` |
||||
|
|
||||
|
### 打包 |
||||
|
|
||||
|
```text |
||||
|
mvn -DskipTests package |
||||
|
BUILD SUCCESS |
||||
|
Total time: 2.439 s |
||||
|
``` |
||||
|
|
||||
|
### MySQL 8.0.43 冒烟 |
||||
|
|
||||
|
实际执行结果: |
||||
|
|
||||
|
```text |
||||
|
id importance_is_proxy |
||||
|
1 0 |
||||
|
2 0 |
||||
|
3 1 |
||||
|
|
||||
|
DATA_TYPE COLUMN_TYPE COLUMN_DEFAULT IS_NULLABLE |
||||
|
tinyint tinyint(1) 0 NO |
||||
|
``` |
||||
|
|
||||
|
说明:MySQL 将 `BOOLEAN` 规范化为 `tinyint(1)` 属正常行为;这与 V37 目标及 H2 的 `BOOLEAN` 元数据要求一致。 |
||||
|
|
||||
|
## 完整 `mvn test` 状态 |
||||
|
|
||||
|
独立执行完整命令: |
||||
|
|
||||
|
```text |
||||
|
mvn test |
||||
|
Tests run: 1396, Failures: 2, Errors: 9, Skipped: 0 |
||||
|
BUILD FAILURE |
||||
|
``` |
||||
|
|
||||
|
失败项仍为与本功能无关的既有问题: |
||||
|
|
||||
|
- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:固定过期时间导致期望 1、实际 0。 |
||||
|
- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:测试仍使用旧服务方法签名,返回 500。 |
||||
|
- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:测试仍 stub 旧 Repository 方法,结果为 null。 |
||||
|
- `LocalResumeStorageServiceTest`:Windows JUnit 临时目录清理的 8 个 `AccessDeniedException` 错误。 |
||||
|
|
||||
|
Skill Cloud 相关测试在完整套件中通过;但按退出条件,完整 `mvn test` 仍未全绿,不能声称全量回归通过。 |
||||
|
|
||||
|
## 范围与遮蔽检查 |
||||
|
|
||||
|
- 本次修复的生产代码变更仅涉及 `OccupationSkill` 和新增 V37;其余变更为相应专项测试的布尔兼容与回归断言。 |
||||
|
- 未修改 `SecurityConfig`、接口、DTO、业务查询逻辑或其他 schema 映射。 |
||||
|
- `SkillTaxonomyRepositoryTest` 原有的 schema filter 仍只限制 H2 测试环境的旧表验证范围,本轮没有扩大或借助该 filter 掩盖 V37 相关 schema 问题;V37 专项迁移测试直接检查 `information_schema` 元数据和约束。 |
||||
|
- 工作区中 `.trae/*` 等文件属于本任务之外的既有未提交内容,不构成本次运行时修复差异。 |
||||
|
|
||||
|
## 非阻断观察 |
||||
|
|
||||
|
1. `V37__skill_cloud_boolean_type.sql` 当前索引版本与工作区版本存在一个仅涉及列注释的文本差异;两者不影响 DDL 语义,但提交前应统一内容,避免最终提交与独立测试的具体文本不一致。 |
||||
|
2. V37 文件工作区版本末尾缺少换行;非功能问题。 |
||||
|
3. MySQL 仅完成 8.0.43 二进制冒烟,未在实际 8.4 二进制上复跑;判断为语法兼容,但如需严格的 8.4 认证应补充运行环境验证。 |
||||
|
|
||||
|
## 剩余阻断 |
||||
|
|
||||
|
- 本次运行时启动修复:无阻断。 |
||||
|
- 项目最终关闭:完整 `mvn test` 仍有 2 failures + 9 errors;需要修复这些既有测试,或由项目按流程正式记录豁免后,才能推进全量回归结论和最终关闭。 |
||||
@ -0,0 +1,14 @@ |
|||||
|
Runtime failure evidence |
||||
|
======================= |
||||
|
Task: manual application startup from IntelliJ |
||||
|
Symptom: Hibernate SessionFactory build failed |
||||
|
Column: occupation_skills.importance_is_proxy |
||||
|
Found: bit (Types#BIT) |
||||
|
Expected: tinyint (Types#TINYINT) |
||||
|
Root cause: OccupationSkill.importanceIsProxy is explicitly forced to SqlTypes.TINYINT, unlike existing Boolean mappings. |
||||
|
|
||||
|
Attempt 2 evidence |
||||
|
================== |
||||
|
Removing @JdbcTypeCode(TINYINT) alone fixed the MySQL expectation but broke H2 schema validation: |
||||
|
found tinyint, expected boolean. |
||||
|
Conclusion: the column must be normalized to a cross-dialect BOOLEAN/BIT form, without modifying already-applied V35. |
||||
@ -0,0 +1,77 @@ |
|||||
|
# N03 行业技能词云来源与覆盖清单 |
||||
|
|
||||
|
## 1. 固定来源 |
||||
|
|
||||
|
| 来源 | 固定版本 | 用途 | 许可/复用条件 | 官方地址与归属 | |
||||
|
| --- | --- | --- | --- | --- | |
||||
|
| ESCO | ESCO dataset v1.2.1(Last update 10/12/2025,发布页记录 Release v5.7.0) | 提供行业/职位映射所需的职业—技能—技能分类主关系、稳定来源 ID 和层级结构 | 依据 Commission Decision 2011/833/EU,可免费下载、使用、复制和复用。文档和产品界面须保留官方署名语句:`This service uses the ESCO classification of the European Commission.` | 数据集下载页:https://esco.ec.europa.eu/en/use-esco/download<br>复用条件说明:https://esco.ec.europa.eu/en/about-esco/faq | |
||||
|
| O*NET | O*NET 31.0 Database | 为 ESCO 未收录的具体技术提供 `software_skills` 补充来源,并通过职业/工作活动 Importance 提供首版词云热度 `heat` | License 为 CC BY 4.0,归属与许可说明见 https://creativecommons.org/licenses/by/4.0/ | 官方数据库页:https://www.onetcenter.org/database.html | |
||||
|
|
||||
|
运行时不得访问 ESCO、O*NET 或任何在线词库服务。所有首版数据必须随 Flyway 迁移进入本地数据库,并保留来源版本与归属信息。 |
||||
|
|
||||
|
## 2. 首版覆盖边界 |
||||
|
|
||||
|
首版只覆盖互联网、金融、教育三类行业及下列 10 个已列代表性职位。该矩阵定义导入上限和最小覆盖范围,不要求在全量上游数据中导入未列出的职位。 |
||||
|
|
||||
|
| 行业 | 代表性职位 | 首批技能分类范围 | |
||||
|
| --- | --- | --- | |
||||
|
| 互联网 | 前端开发工程师 | 编程语言、Web 基础、前端框架、工程化工具、浏览器与调试、测试、版本控制与协作 | |
||||
|
| 互联网 | 后端开发工程师 | 编程语言、后端框架与平台、数据库、缓存与消息、云/DevOps、测试、工程化工具、安全 | |
||||
|
| 互联网 | 测试工程师 | 编程语言、测试设计、自动化测试框架、接口与数据库测试、性能测试、CI/CD 与工程化工具 | |
||||
|
| 互联网 | DevOps 工程师 | 编程语言与脚本、云平台、容器与编排、CI/CD、配置管理与 IaC、可观测性、安全、数据库 | |
||||
|
| 金融 | 金融数据分析师 | 编程语言、数据分析与可视化、数据库与数据仓库、统计与建模、金融计算框架/平台、金融领域知识、工程化工具 | |
||||
|
| 金融 | 风控建模工程师 | 编程语言、统计与建模、风险模型、建模框架/平台、数据处理与数据库、模型验证、部署与工程化工具 | |
||||
|
| 金融 | 量化开发工程师 | 编程语言、量化建模框架/平台、市场与金融数据、数据库、低延迟与高性能工程、测试、云/DevOps | |
||||
|
| 教育 | 教育产品经理 | 教育领域知识、产品分析方法、需求与流程、数据分析、教育平台与工具、协作与工程化工具 | |
||||
|
| 教育 | 教学设计师 | 教学设计、课程与学习平台、数字化内容工具、评估设计、学习数据分析、无障碍与规范 | |
||||
|
| 教育 | 教育数据分析师 | 编程语言、数据分析与可视化、数据库与 SQL、统计与建模、教育领域知识、报表与工程化工具 | |
||||
|
计数说明:任务给出的逐项职位清单为互联网 4 个、金融 3 个、教育 3 个,共 10 个。验收文字中的“11 个职位”与该枚举不一致;本清单严格覆盖逐项列出的 10 个职位,不自行新增未批准职位。 |
||||
|
|
||||
|
矩阵中的分类是每个职位首批应覆盖的能力域;T3 只导入这些分类下已确定稳定来源 ID、标准技能和中文标准名的技能,不在此文档中展开具体 `INSERT` 行。 |
||||
|
|
||||
|
## 3. 导入规则 |
||||
|
|
||||
|
1. **职责分工**:T2 只建立七张词库表、外键、唯一约束和查询索引;T3 只导入上述三类行业、10 个已列代表性职位及其关联技能分类、技能、别名和 O*NET Importance。 |
||||
|
2. **代表性子集与混合来源**:不导入 ESCO/O*NET 全量数据;ESCO 是职位、通用技能和分类主表;ESCO 无可靠节点的具体技术使用 O*NET `software_skills` 补充;不把平台自由文本岗位或行业直接当作标准词库节点。 |
||||
|
3. **来源 ID 不变**:ESCO/O*NET 来源 ID 原样保存在稳定来源字段中;项目不得重编号、覆盖或伪造上游 ID。 |
||||
|
4. **中文标准名必填**:每个入选技能必须有非空 `name_zh` 中文标准名;中文名称由项目维护,但不替代或修改上游来源 ID。 |
||||
|
5. **热度来源**:每条非空 Importance 必须保存 O*NET SOC、Element ID 和代理标记;技能无法映射 O*NET Importance 时必须保留,写入 `importance=null`;查询时映射为 `heat=null`,并排在有热度技能之后。 |
||||
|
6. **项目映射责任**:项目维护中文名称、别名、技能分类和行业/职位映射,不修改开源来源 ID。行业/职位映射、细粒度分类和跨源技能对应关系均作为项目适配层记录。 |
||||
|
7. **别名归一化**:生成 `normalized_alias` 时依次执行 Unicode NFKC、首尾空白移除、大小写折叠、连字符转空格、连续空白折叠为单空格、句点移除;`+` 和 `#` 必须保留,不能删除或替换。 |
||||
|
8. **来源可追溯**:来源清单写入 `skill_source_versions`,至少记录来源名称、固定版本、许可、归属 URL 和导入时间;O*NET 补充技术的 `source_code` 使用真实 `SOC|Workplace Example|Element ID` 组合,职位技能关系记录 O*NET SOC、Element ID、代理标记及关联的来源版本。 |
||||
|
9. **迁移不可改写**:T2/T3 计划使用 `V35__skill_cloud_taxonomy.sql` 和 `V36__skill_cloud_seed.sql`。执行前若编号已被占用,必须回退规划并刷新编号;后续词库更新只能新增 Flyway 迁移,不得改写 V1–V34 或任何已执行迁移。 |
||||
|
10. **运行时离线**:接口运行时不得调用外部词库、AI、课程平台或同步服务;排名只读取本地 Flyway 数据。 |
||||
|
|
||||
|
## 4. 来源版本、许可与归属 URL |
||||
|
|
||||
|
| 数据落点 | 来源名称 | 版本字段固定值 | 许可字段固定值 | 归属 URL | |
||||
|
| --- | --- | --- | --- | --- | |
||||
|
| 职业、技能、技能分类及关系 | ESCO | `1.2.1 (Release v5.7.0, Last update 10/12/2025)` | `Commission Decision 2011/833/EU` | https://esco.ec.europa.eu/en/use-esco/download | |
||||
|
| 职位技能 Importance | O*NET | `31.0 Database` | `CC BY 4.0` | https://www.onetcenter.org/database.html | |
||||
|
|
||||
|
ESCO 产品署名必须使用:`This service uses the ESCO classification of the European Commission.` |
||||
|
|
||||
|
## 5. 映射责任边界 |
||||
|
|
||||
|
| 事项 | 上游来源责任 | 项目责任 | |
||||
|
| --- | --- | --- | |
||||
|
| ESCO 职业、技能、分类层级和来源 ID | 固定且可追溯 | 原样保存,不修改 ID | |
||||
|
| O*NET Importance 数值 | 固定且可追溯 | 原样保存;无映射时写 `null`,不造权重 | |
||||
|
| 中文名称和别名 | 不保证完整中文覆盖 | 补齐中文标准名、别名及归一化值 | |
||||
|
| 细粒度技能分类 | 不保证符合本项目展示粒度 | 在职位下维护分类和展示名称 | |
||||
|
| 行业/职位映射 | 不保证覆盖三类项目行业和中文职位名 | 维护行业—职位—分类—技能映射 | |
||||
|
| 跨源技能与重要性关联 | 不提供项目所需的稳定本地关系 | 维护 ESCO 技能到 O*NET `software_skills` 的补充映射,以及每条 Importance 的 SOC/Element ID/代理标记 | |
||||
|
|
||||
|
## 6. 验收检查项 |
||||
|
|
||||
|
- AC-1:`changes/n03-skill-cloud/data/source-manifest.md` 与 `docs/skill-taxonomy-sources.md` 均存在且非空,来源版本、许可、归属 URL、覆盖矩阵和导入规则一致。 |
||||
|
- AC-2:文档明确覆盖互联网、金融、教育三类行业及全部 10 个已列代表性职位,并为每个职位给出首批技能分类范围。 |
||||
|
- AC-3:文档明确固定 ESCO dataset v1.2.1(Last update 10/12/2025,发布页记录 Release v5.7.0)和 O*NET 31.0 Database。 |
||||
|
- AC-4:文档明确 ESCO 依据 Commission Decision 2011/833/EU 可免费下载、使用、复制和复用,并原样保留指定署名语句。 |
||||
|
- AC-5:文档明确 O*NET License 为 CC BY 4.0,并记录官方数据库页与许可 URL。 |
||||
|
- AC-6:文档明确项目维护中文名称、别名、技能分类和行业/职位映射,不修改开源来源 ID。 |
||||
|
- AC-7:文档明确 T2 建结构、T3 导数据,只导入代表性职位和关联技能;技能中文标准名必填。 |
||||
|
- AC-8:文档明确 O*NET 补充技术、每条 Importance 的 SOC/Element ID/代理标记,以及无映射时保留技能并写入 `importance=null`、查询侧映射为 `heat=null`。 |
||||
|
- AC-9:文档明确别名归一化遵循 NFKC、首尾空白、大小写、连字符、连续空白、句点规则,且保留 `+`、`#`。 |
||||
|
- AC-10:文档明确后续词库更新只能新增 Flyway 迁移,不得改写 V1–V34 或已执行迁移。 |
||||
|
- AC-11:来源、覆盖、导入与责任边界中不得出现未决占位或延期确认语句。 |
||||
@ -0,0 +1,55 @@ |
|||||
|
# N03 Decision Point Audit |
||||
|
|
||||
|
审计时间:2026-09-15T08:40:05Z |
||||
|
|
||||
|
## Decision Point Status |
||||
|
|
||||
|
| DP | Status | Evidence | |
||||
|
| --- | --- | --- | |
||||
|
| DP-0 | confirmed | 需求、范围、非目标、语言与 Full workflow 已确认 | |
||||
|
| DP-1 | confirmed | 三接口需求口径已确认 | |
||||
|
| DP-2 | approved | proposal、三份 delta specs、design、tasks 已审阅 | |
||||
|
| DP-3 | approved | execution contract revision 2 已批准 | |
||||
|
| DP-4 | approved | SDD;W1/W2/W3 串行 wave,依赖关系已固化 | |
||||
|
| DP-5 | adjudicated | T3 来源与可证伪性修复获人工批准 | |
||||
|
| DP-6 | pass with formal waiver | 功能范围内验证通过;全量测试中的既有无关失败由用户正式豁免 | |
||||
|
| DP-7 | pending | 等待归档确认 | |
||||
|
|
||||
|
## Contract Coverage |
||||
|
|
||||
|
- W1:来源清单、V35 词库结构、V36 代表性种子数据、可追溯 O*NET provenance。 |
||||
|
- W2:Repository、词云排序、个人匹配、学习路径,50 项 wave 测试通过。 |
||||
|
- W3:三个 STUDENT 接口、真实权限测试、端到端一致性、接口文档和 OpenAPI。 |
||||
|
- 14 条 MUST/SHALL 行为均有实现与测试证据。 |
||||
|
- delta specs 已发布到根 baseline:specs/skill-cloud、specs/skill-match、specs/learning-path,共 14 条 Requirements,无冲突。 |
||||
|
|
||||
|
## Verification Evidence |
||||
|
|
||||
|
- Skill Cloud 指定测试组:74 tests,0 failures,0 errors,BUILD SUCCESS。 |
||||
|
- mvn -DskipTests package:BUILD SUCCESS。 |
||||
|
- python docs\verify_openapi.py:paths 129,All checks passed。 |
||||
|
- 启动缺陷修复:V37 将 importance_is_proxy 规范化为跨方言 BOOLEAN;H2 MODE=MySQL 与 MySQL 8.0.43 冒烟通过。 |
||||
|
- 完整 mvn test:1396 tests,2 failures,1 error,BUILD FAILURE。 |
||||
|
- 本次完整测试失败: |
||||
|
- CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries:既有时间依赖。 |
||||
|
- JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores:既有 Mock 签名漂移。 |
||||
|
- JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1:既有 Repository 方法签名漂移。 |
||||
|
- 这些失败位于本变更未修改的模块;Skill Cloud 测试在全量套件中全部通过。 |
||||
|
|
||||
|
## Formal Waiver |
||||
|
|
||||
|
用户于 2026-09-15 明确批准:将与本功能无关的既有失败作为正式豁免记录,并进入 release-archivist。当前 fresh run 只剩上述 3 条历史阻塞;更早一轮曾包含 LocalResumeStorageServiceTest 的 Windows 临时目录错误,后续环境运行已恢复。本豁免覆盖所有与本功能无关、且在变更前后可独立复现的既有失败。 |
||||
|
|
||||
|
## Scope and Coherence |
||||
|
|
||||
|
- 变更实现集中在 com.mydreamjob.skill、Flyway V35–V37、SecurityConfig、docs、OpenAPI 与对应测试。 |
||||
|
- 未修改 V1–V34 历史迁移或已有接口语义。 |
||||
|
- V37 是启动兼容修复,不改变 API 行为或业务公式。 |
||||
|
- W2/W3 独立评审均已通过;运行时启动修复也已独立复审 PASS。 |
||||
|
- 已知非阻断项:heatSource 设计表述与实际 heatSourceCode/heatElementId/heatProxy 字段命名不同;文档已按实际字段发布。 |
||||
|
|
||||
|
## Remaining Actions |
||||
|
|
||||
|
1. 记录 DP-7 归档确认。 |
||||
|
2. 将状态从 executing 转为 closing。 |
||||
|
3. ssf CLI 不可用;物理归档/合并需在获得用户确认后手工执行,且必须排除仓库中已有的无关未提交文件。 |
||||
@ -0,0 +1,180 @@ |
|||||
|
# 行业技能词云与学习路径设计 |
||||
|
|
||||
|
## 事实与约束 |
||||
|
|
||||
|
- 仓库当前为 Spring Boot 3.5.16、Java 17、MySQL/Flyway/JPA,最新已存在迁移为 `V34`。 |
||||
|
- 学生技能存储在 `student_profiles.skills_json`,历史数据可能为空、重复或无法解析。 |
||||
|
- 企业行业与岗位业务数据是自由文本,不能作为稳定的“行业 → 职位 → 技能”分类源。 |
||||
|
- ESCO 提供职位、技能和技能分类关系;O*NET 提供 Importance 数值。两者均需保留来源版本与许可归属。 |
||||
|
- 首版只导入互联网、金融、教育及代表性职位,避免全量翻译和超大迁移。 |
||||
|
- 三个接口均为只读、仅 STUDENT,身份只取 JWT,响应统一使用 `ApiResponse`。 |
||||
|
- 词云热度是静态 O*NET Importance,不随平台岗位发布和投递行为变化。 |
||||
|
- 计算完全确定,不调用 AI、网络课程服务或第三方运行时接口。 |
||||
|
|
||||
|
## Goals |
||||
|
|
||||
|
- 建立可持续扩展的开源技能知识库,并允许项目维护中文名称、别名和细粒度分类。 |
||||
|
- 提供完整、稳定、可筛选的热度排序技能词云。 |
||||
|
- 以同一排序结果计算个人 TOP10 匹配度,避免不同接口结果不一致。 |
||||
|
- 基于未命中技能生成最小可用的学习优先级和建议。 |
||||
|
- 保持现有模块接口语义不变,并满足项目测试、OpenAPI 和黑盒验收约定。 |
||||
|
|
||||
|
## Non-Goals |
||||
|
|
||||
|
- 不做岗位实时文本挖掘、平台岗位热度聚合或用户行为周榜。 |
||||
|
- 不导入 ESCO/O*NET 全量数据,不建设后台维护界面。 |
||||
|
- 不保存匹配快照、学习进度、完成状态或提醒。 |
||||
|
- 不提供课程、讲师、价格、时长、链接和 AI 对话。 |
||||
|
- 不向 COMPANY/ADMIN 暴露个人匹配和学习路径。 |
||||
|
|
||||
|
## Decisions |
||||
|
|
||||
|
### D1: 以 ESCO + O*NET 作为静态知识底座 |
||||
|
|
||||
|
**Choice**:ESCO 作为职位、通用技能和分类主表;当具体技术(如 React、Docker、Spring Boot)没有可靠 ESCO 节点时,允许使用 O*NET `software_skills` 作为补充技能来源。O*NET 继续提供可追溯 Importance;项目只维护中文标签、别名和映射。 |
||||
|
|
||||
|
**Rationale**:能用开源数据覆盖技能层级,同时保留 O*NET 明确的重要度字段,不需要自行创造热度权重。 |
||||
|
|
||||
|
**Alternatives**: |
||||
|
- 仅 O*NET:重要度完整,但英文和美国职业体系适配成本高。 |
||||
|
- 严格仅 ESCO:来源单一,但会丢失 React、Docker 等 ESCO 未收录的具体技能。 |
||||
|
- 自建词典:可控性高,但需要长期运营且缺少来源可信度。 |
||||
|
|
||||
|
**Consequences**:需要明确区分 ESCO 主来源技能与 O*NET 补充技术;每条热度必须记录 O*NET SOC 和 Element ID,无法映射时保留技能并写空热度。 |
||||
|
|
||||
|
### D2: 词库使用关系表落 Flyway |
||||
|
|
||||
|
**Choice**:新增行业、职位、技能分类、技能、别名、职位技能关系和来源版本表,通过 Flyway 迁移导入代表性数据。 |
||||
|
|
||||
|
**Rationale**:接口需要多条件过滤、稳定排序和可追溯来源;关系表可利用外键、唯一约束和索引保证数据质量。 |
||||
|
|
||||
|
**Alternatives**: |
||||
|
- 静态 JSON:实现快,但无法方便地按节点过滤、校验引用和演进。 |
||||
|
- 图数据库:适合复杂关系,但超出当前技术栈和首版范围。 |
||||
|
|
||||
|
**Consequences**:迁移文件需要严格审查,后续词库更新必须创建新迁移,不能修改已执行迁移。 |
||||
|
|
||||
|
### D3: 三个接口共享同一条排名管道 |
||||
|
|
||||
|
**Choice**:先查询筛选范围内的技能列表并按热度排序,再从同一列表截取 TOP10;匹配和学习路径均调用同一领域服务,不重新排序。 |
||||
|
|
||||
|
**Rationale**:词云、匹配度和学习路径必须对“热门技能集合”保持一致,减少重复逻辑和边界分歧。 |
||||
|
|
||||
|
**Alternatives**: |
||||
|
- 每个接口独立查询和排序:实现分散,容易出现排序不一致。 |
||||
|
- 持久化统一排名快照:可缓存,但当前数据规模不需要额外调度和一致性成本。 |
||||
|
|
||||
|
**Consequences**:排序规则、空热度规则和别名规则只有一个实现位置;单元测试必须聚焦该共享管道。 |
||||
|
|
||||
|
### D4: TOP10 不单独存储或返回 |
||||
|
|
||||
|
**Choice**:词云接口返回完整排序列表,前端直接取前十条;匹配服务在内部最多取前十条。 |
||||
|
|
||||
|
**Rationale**:用户已确认不需要独立 TOP10 数据源,列表本身是唯一事实来源。 |
||||
|
|
||||
|
**Alternatives**:响应同时返回 `top10` 字段或提供独立接口;会重复数据并增加一致性风险。 |
||||
|
|
||||
|
**Consequences**:前端必须按约定截取前十条;接口文档需明确列表已按排名排序。 |
||||
|
|
||||
|
### D5: 匹配和建议按请求实时计算且不持久化 |
||||
|
|
||||
|
**Choice**:每次读取当前学生技能、词库和筛选条件后计算匹配度和学习路径,不保存计算结果。 |
||||
|
|
||||
|
**Rationale**:数据量小、算法确定,且用户未要求历史记录;避免引入无用状态和隐私数据留存。 |
||||
|
|
||||
|
**Alternatives**:保存每日匹配快照或学习计划;会增加表、清理与一致性复杂度,首版无收益。 |
||||
|
|
||||
|
**Consequences**:学生技能或词库更新后,结果立即变化;响应必须包含足够字段让前端独立展示。 |
||||
|
|
||||
|
### D6: 学习建议使用分类模板 |
||||
|
|
||||
|
**Choice**:技能分类映射到编程语言、框架、工具和通用四类建议模板。 |
||||
|
|
||||
|
**Rationale**:满足“技能 + 优先级 + 简化建议”,无需依赖外部课程数据。 |
||||
|
|
||||
|
**Alternatives**:调用 AI 或维护课程表;前者不确定且超出约束,后者缺少可用授权数据。 |
||||
|
|
||||
|
**Consequences**:建议保持简短、行业无关和可重复;后续可在不改变接口结构的前提下细化模板。 |
||||
|
|
||||
|
## Data Model |
||||
|
|
||||
|
| 表 | 关键字段 | 作用 | |
||||
|
| --- | --- | --- | |
||||
|
| `skill_industries` | `id`, `code`, `name_zh`, `source_code`, `sort_order` | 行业节点 | |
||||
|
| `skill_occupations` | `id`, `industry_id`, `code`, `name_zh`, `source_code`, `sort_order` | 行业下职位 | |
||||
|
| `skill_categories` | `id`, `occupation_id`, `code`, `name_zh`, `suggestion_template_code`, `sort_order` | 职位下技能分类 | |
||||
|
| `skills` | `id`, `code`, `name_zh`, `name_en`, `source_type`, `source_code` | 标准技能 | |
||||
|
| `skill_aliases` | `id`, `skill_id`, `alias`, `normalized_alias` | 中英文、大小写和写法别名 | |
||||
|
| `occupation_skills` | `occupation_id`, `skill_id`, `category_id`, `importance`, `importance_source_code`, `importance_element_id`, `importance_is_proxy`, `relationship_type`, `source_version` | 职位技能关系、热度及其机器可追溯来源 | |
||||
|
| `skill_source_versions` | `source_name`, `source_version`, `license`, `attribution_url`, `imported_at` | 来源与许可追溯 | |
||||
|
|
||||
|
约束与索引: |
||||
|
|
||||
|
- `skill_occupations.industry_id + code` 唯一,防止同一行业内职位重复。 |
||||
|
- `skill_categories.occupation_id + code` 唯一。 |
||||
|
- `skill_aliases.normalized_alias` 全局唯一,避免同一写法映射到多个技能。 |
||||
|
- `occupation_skills.occupation_id + skill_id` 唯一。 |
||||
|
- 每条非空 `importance` 必须有 O*NET `importance_source_code` 和 `importance_element_id`;代理映射必须设置 `importance_is_proxy=1`。 |
||||
|
- 查询索引覆盖 `occupation_id + category_id` 和 `occupation_id + importance`。 |
||||
|
- `category_id` 必须属于 `occupation_id`,通过服务校验并在必要测试中验证。 |
||||
|
|
||||
|
## API Contract |
||||
|
|
||||
|
### `GET /api/v1/skills/word-cloud` |
||||
|
|
||||
|
查询参数:`industryId` 必填,`occupationId` 必填,`categoryId` 可选。 |
||||
|
|
||||
|
返回 `data`: |
||||
|
|
||||
|
- `industry`、`occupation`、`category`:实际筛选节点。 |
||||
|
- `total`:技能总数。 |
||||
|
- `items`:按 `heat DESC, nameZh ASC, skillId ASC` 排序,包含 `rank`、`skillId`、`code`、`nameZh`、`nameEn`、`categoryId`、`categoryName`、`heat`、`heatSource`。 |
||||
|
|
||||
|
### `GET /api/v1/skills/match` |
||||
|
|
||||
|
使用与词云相同的三个查询参数。返回 `data`: |
||||
|
|
||||
|
- `matchScore`:0–100 整数。 |
||||
|
- `matchedCount` 与 `totalCount`。 |
||||
|
- `matchedSkills` 与 `missingSkills`:使用精简技能项,缺失技能保持热度顺序。 |
||||
|
|
||||
|
### `GET /api/v1/skills/learning-path` |
||||
|
|
||||
|
使用与词云相同的三个查询参数。返回 `data`: |
||||
|
|
||||
|
- `total`。 |
||||
|
- `items`:每项包含 `priority`、`skillId`、`skillName`、`categoryId`、`categoryName`、`suggestion`。 |
||||
|
|
||||
|
## Processing Rules |
||||
|
|
||||
|
1. Flyway 先建立表和约束,再导入代表性词库数据。 |
||||
|
2. 查询服务校验行业、职位、分类关系;ESCO 作为职位与通用技能主表,O*NET `software_skills` 仅补充 ESCO 缺失的具体技术,构建统一的排序技能列表。 |
||||
|
3. 匹配服务解析当前学生 `skills_json`;解析失败按空技能处理并记录告警,不输出原始 JSON。 |
||||
|
4. 学生技能和技能别名统一执行 Unicode 规范化、去首尾空白、大小写折叠、连续空白折叠及常见标点归一化。 |
||||
|
5. TOP10 大小取 `min(10, 列表大小)`;匹配度按 `round(matched * 100.0 / size)` 计算,`size=0` 时返回 0。 |
||||
|
6. 学习路径遍历缺失技能,按列表顺序生成连续优先级,并依据 `suggestion_template_code` 产生建议。 |
||||
|
7. 所有计算在只读事务中完成,不写入匹配或学习路径记录。 |
||||
|
|
||||
|
## Security and Privacy |
||||
|
|
||||
|
- 在 SecurityFilterChain 中将 `/skills/**` 限制为已认证请求,Controller/Service 再断言 STUDENT 角色。 |
||||
|
- 身份只取 SecurityContext/JWT,不接受 `studentUserId`、`userId` 或 `skills` 请求参数。 |
||||
|
- 响应不包含原始技能 JSON、手机号、邮箱、简历内容或词库内部敏感字段。 |
||||
|
- 日志只记录异常类型与请求上下文,不记录完整学生技能数组。 |
||||
|
|
||||
|
## Risks and Verification |
||||
|
|
||||
|
| 风险 | 处理 | 验证证据 | |
||||
|
| --- | --- | --- | |
||||
|
| ESCO/O*NET 中文覆盖不足 | 首版只导入代表性职位,所有入选技能必须有中文标准名 | 种子数据检查:中文名非空率 100% | |
||||
|
| 别名冲突导致匹配不确定 | `normalized_alias` 全局唯一,迁移失败即阻断 | Flyway 唯一约束测试和别名单测 | |
||||
|
| Importance 缺失 | `heat=null`,排序末位,保留技能 | 排序单元测试和 H2 Repository 测试 | |
||||
|
| 分类关系错配 | 校验分类属于职位、职位属于行业 | 非法组合 MockMvc 400 场景 | |
||||
|
| 迁移过大影响启动 | 首版限制三类行业和代表性职位,种子数据可控 | 迁移在 H2/MySQL 测试库执行耗时记录 | |
||||
|
| 学生技能 JSON 损坏 | 捕获解析异常并按空技能处理 | 异常 JSON 服务测试,断言 200 和匹配度 0 | |
||||
|
| 三个接口结果漂移 | 使用共享排名服务与同一 DTO 排序 | 集成测试断言 TOP10、匹配和学习路径顺序一致 | |
||||
|
| 许可或版本遗漏 | 保存来源版本、许可和归属 URL | 来源说明文档审查与迁移数据断言 | |
||||
|
|
||||
|
## Completion Boundary |
||||
|
|
||||
|
设计完成的标准是:四个规划产物一致、所有需求有可测试场景、任务覆盖三个接口和 Flyway 数据变更、没有未确认的兼容性或权限决策。实现和验收不在本设计阶段启动。 |
||||
@ -0,0 +1,147 @@ |
|||||
|
# N03 行业技能词云与学习路径执行契约 |
||||
|
|
||||
|
## Intent Lock |
||||
|
|
||||
|
**问题**:学生无法从行业和职位维度查看招聘市场热门技能,无法量化自己相对 TOP10 的掌握程度,也没有针对缺失技能的优先级建议。 |
||||
|
|
||||
|
**意图**:基于 ESCO + O*NET 开源知识底座,通过 Flyway 建立可追溯的行业—职位—技能分类—技能数据。ESCO 作为职位、通用技能和分类主表,O*NET `software_skills` 仅补充 ESCO 缺失的具体技术;O*NET Importance 提供热度及机器可追溯来源。系统提供仅 STUDENT 可访问的词云、个人匹配度和学习路径三个确定性接口。 |
||||
|
|
||||
|
**完成证明**:V35/V36 迁移成功执行;三个接口按同一热度列表工作;权限、排序、别名、匹配分母、空数据和通用学习建议均通过自动化测试;OpenAPI 与文档一致。 |
||||
|
|
||||
|
## Scope Fence |
||||
|
|
||||
|
### 允许范围 |
||||
|
|
||||
|
- 新增 `mydreamjob-server/src/main/resources/db/migration/V35__skill_cloud_taxonomy.sql` 和 `V36__skill_cloud_seed.sql`。 |
||||
|
- 新增 `com.mydreamjob.skill` 领域包、Repository、Service、DTO 和三个 Controller。 |
||||
|
- 修改安全配置以保护 `/skills/**`,仅允许已登录 STUDENT。 |
||||
|
- 新增 ESCO/O*NET 来源说明、接口文档、OpenAPI 契约和测试。\n- 新增从 O*NET 31.0 官方 CSV 提取的测试证据快照,仅用于全量核对种子来源和 Importance,不参与运行时。 |
||||
|
- 首版数据只覆盖互联网、金融、教育及代表性职位。 |
||||
|
- 具体技术无可靠 ESCO 节点时,允许使用 O*NET `software_skills` 作为补充来源。 |
||||
|
- 词云热度只取 O*NET Importance;无映射时 `heat=null`。 |
||||
|
- 匹配度使用当前 TOP10 和实际技能数;学习路径只返回缺失技能、优先级和通用建议。 |
||||
|
|
||||
|
### 禁止范围 |
||||
|
|
||||
|
- 不修改 V1–V34 历史迁移,不改变已有接口语义。 |
||||
|
- 不接 AI、课程平台、第三方运行时接口或在线词库同步。 |
||||
|
- 不按平台岗位 `jobId`、发布量或用户行为计算热度。 |
||||
|
- 不实现后台词库维护、学习进度、提醒、历史快照或前端页面。 |
||||
|
- 不向 COMPANY/ADMIN 返回个人匹配或学习路径。 |
||||
|
- 不在响应或日志中输出原始 `skills_json`、手机号、邮箱、简历内容。 |
||||
|
|
||||
|
## Approved Behavior |
||||
|
|
||||
|
| ID | 已批准行为 | 批次 | |
||||
|
| --- | --- | --- | |
||||
|
| REQ-SC-1 | `/skills/word-cloud`、`/skills/match`、`/skills/learning-path` 只允许已登录 STUDENT;匿名返回 401,COMPANY/ADMIN 返回 403。 | B3 | |
||||
|
| REQ-SC-2 | `industryId`、`occupationId` 必填且必须互为关联;`categoryId` 可选,存在时必须属于目标职位;非法组合返回 400。 | B2、B3 | |
||||
|
| REQ-SC-3 | 词云返回筛选范围内全部技能,按 `heat DESC, nameZh ASC, skillId ASC` 排序;`heat=null` 排在有值技能之后。 | B2 | |
||||
|
| REQ-SC-4 | TOP10 不单独存储或返回,前端取词云前十条;少于十条时全部结果构成实际 TOP10。 | B2、B3 | |
||||
|
| REQ-SC-5 | 每个技能项包含技能 ID、名称、分类、`heat`、稳定排名和可追溯来源标识;响应使用 `ApiResponse`。 | B2、B3 | |
||||
|
| REQ-SM-1 | 个人匹配只使用当前筛选结果的 `min(10, actualSize)` 个技能,不另建排序。 | B2 | |
||||
|
| REQ-SM-2 | 学生技能只从 JWT 对应学生的 `student_profiles.skills_json` 读取;缺失、空数组或损坏 JSON 按无技能处理。 | B2 | |
||||
|
| REQ-SM-3 | 匹配使用标准技能和别名,执行 NFKC、首尾空白、大小写、连字符转空格、连续空白折叠和句点移除归一化;`+`、`#` 不得删除。 | B2 | |
||||
|
| REQ-SM-4 | 匹配度为 `round(matched * 100 / min(10, actualSize))`;`actualSize=0` 时返回 0;重复命中同一技能只计一次。 | B2 | |
||||
|
| REQ-SM-5 | 响应分别返回命中与缺失技能,缺失列表保持 TOP10 顺序,两者并集等于实际 TOP10。 | B2、B3 | |
||||
|
| REQ-LP-1 | 学习路径只包含当前 TOP10 的未命中技能,不包含已命中或 TOP10 之外的技能。 | B2 | |
||||
|
| REQ-LP-2 | 学习路径沿用词云顺序,优先级从 1 连续编号;空热度技能仍在有权重技能之后。 | B2 | |
||||
|
| REQ-LP-3 | 学习建议由编程语言、框架、工具、通用四类确定性模板生成;不调用 AI 或外部服务,不含课程链接、时长或价格。 | B2 | |
||||
|
| REQ-LP-4 | 每项只返回技能 ID、名称、分类、优先级和简化建议;无缺口时返回空列表和总数 0。 | B2、B3 | |
||||
|
|
||||
|
需求覆盖结果:三份 spec 中全部 14 条 `MUST`/`SHALL` 行为均已映射,无未映射需求。 |
||||
|
|
||||
|
## Architecture and Interface Constraints |
||||
|
|
||||
|
- 使用 ESCO 作为职位、通用技能和分类主关系;具体技术无可靠 ESCO 节点时,允许使用 O*NET `software_skills` 作为补充技能来源。 |
||||
|
- O*NET Importance 作为 `heat`;项目维护中文名称、别名和细粒度分类。 |
||||
|
- 数据模型固定为 `skill_industries`、`skill_occupations`、`skill_categories`、`skills`、`skill_aliases`、`occupation_skills`、`skill_source_versions` 七张表。 |
||||
|
- `skill_aliases.normalized_alias` 全局唯一;`occupation_skills(occupation_id, skill_id)` 唯一;分类必须属于职位,职位必须属于行业。 |
||||
|
- `occupation_skills` 必须保存 `importance_source_code`、`importance_element_id` 和 `importance_is_proxy`;非空 `importance` 必须可追溯到 O*NET 31.0 CSV。 |
||||
|
- 三个接口共享同一个只读排名管道和技能投影;不得复制三套排序规则。 |
||||
|
- 接口路径固定为: |
||||
|
- `GET /api/v1/skills/word-cloud` |
||||
|
- `GET /api/v1/skills/match` |
||||
|
- `GET /api/v1/skills/learning-path` |
||||
|
- 三个接口查询参数均为 `industryId`、`occupationId` 必填,`categoryId` 可选。 |
||||
|
- 计算按请求实时完成,不持久化匹配或学习路径。 |
||||
|
- 学生技能解析异常记录不含原始内容的告警,接口返回 200 和空技能匹配结果。 |
||||
|
- 首版只使用代表性数据;迁移编号以当前仓库下一个可用版本为准。若合并前 V35/V36 已被其他变更占用,必须回退规划阶段刷新编号和契约。 |
||||
|
|
||||
|
## Execution Batches |
||||
|
|
||||
|
### Batch B1: 数据与来源基础 |
||||
|
|
||||
|
- 覆盖任务:T1、T2、T3,T3 在第 1 次评审后执行来源与分类修复。 |
||||
|
- 交付物:来源清单、`V35__skill_cloud_taxonomy.sql`、`V36__skill_cloud_seed.sql`、迁移测试。 |
||||
|
- 完成定义:代表性数据包含三类行业和约定职位;所有入选技能有中文标准名;ESCO 职位来源、O*NET 补充技术、每个 Importance 的 SOC/Element ID 均可机器追溯;自动化测试通过版本化证据快照全量核对 O*NET 来源和数值。 |
||||
|
- 依赖:无。 |
||||
|
- 评审门:B1 完成并由 `code-reviewer` 记录 `pass` 后才能开始 B2。 |
||||
|
|
||||
|
### Batch B2: 排名、匹配与学习路径 |
||||
|
|
||||
|
- 覆盖任务:T4、T5、T6、T7。 |
||||
|
- 交付物:实体/Repository、共享词云查询排名服务、别名归一化、个人匹配服务、学习路径模板服务及单元测试。 |
||||
|
- 完成定义:REQ-SC-2 至 REQ-SC-5、REQ-SM-1 至 REQ-SM-5、REQ-LP-1 至 REQ-LP-4 的领域行为可重复且确定。 |
||||
|
- 依赖:B1 通过评审。 |
||||
|
- 评审门:B2 完成并由 `code-reviewer` 记录 `pass` 后才能开始 B3。 |
||||
|
|
||||
|
### Batch B3: 接口、权限、契约与回归 |
||||
|
|
||||
|
- 覆盖任务:T8、T9、T10。 |
||||
|
- 交付物:三个 Controller、权限配置、MockMvc/H2 集成测试、`docs/skill-cloud-api.md`、OpenAPI 和前端联调文档更新。 |
||||
|
- 完成定义:REQ-SC-1 至 REQ-SC-5、REQ-SM-5、REQ-LP-4 的接口行为通过;匿名 401、非 STUDENT 403、非法参数 400;文档与运行时一致。 |
||||
|
- 依赖:B2 通过评审。 |
||||
|
- 评审门:B3 完成并由 `code-reviewer` 记录 `pass` 后才能进入验证和收尾。 |
||||
|
|
||||
|
## Test Obligations |
||||
|
|
||||
|
| ID | 测试义务 | 批次 | 证据 | |
||||
|
| --- | --- | --- | --- | |
||||
|
| TO-1 | Flyway 创建七张表、外键、唯一约束和索引,中文名与来源 ID 完整。 | B1 | `mvn -Dtest=SkillTaxonomyMigrationTest test` | |
||||
|
| TO-2 | 行业/职位/分类关系校验,非法组合返回 400。 | B2、B3 | Repository/Service 单测与 MockMvc | |
||||
|
| TO-3 | 热度降序、空热度末位、同热度稳定排序、空范围列表。 | B2 | `mvn -Dtest=SkillCloudQueryServiceTest test` | |
||||
|
| TO-4 | TOP10 少于十条时使用实际数量,匹配分母为实际数量,`1/3` 四舍五入为 33。 | B2 | `mvn -Dtest=SkillMatchServiceTest test` | |
||||
|
| TO-5 | 大小写、空格、连字符、句点、`C++`、`C#` 和重复别名匹配。 | B2 | 归一化与匹配单测 | |
||||
|
| TO-6 | 无技能、无资料、损坏 JSON、全部命中和全部未命中场景。 | B2、B3 | Service 与 MockMvc 测试 | |
||||
|
| TO-7 | 学习路径只含未命中技能,优先级连续,无缺口返回空列表。 | B2 | `mvn -Dtest=SkillLearningPathServiceTest test` | |
||||
|
| TO-8 | 四类建议模板稳定、行业无关,不含链接、时长、价格或 AI 内容。 | B2 | 模板单测 | |
||||
|
| TO-9 | 三个接口权限、响应结构和参数错误符合契约。 | B3 | `mvn -Dtest=SkillCloudControllerTest test` | |
||||
|
| TO-10 | 三个接口复用同一 TOP10,响应不含原始 `skills_json`。 | B3 | H2 集成测试 | |
||||
|
| TO-11 | 全量回归、打包和静态 OpenAPI 契约通过。 | B3 | `mvn test`、`mvn clean verify`、`python docs\verify_openapi.py` |\n| TO-12 | 测试读取 O*NET 31.0 证据快照,全量核对 47 条 O*NET 技术来源及全部非空 Importance;任一来源不存在或数值被篡改都必须失败。 | B1 | `mvn -Dtest=SkillCloudSeedDataIntegrationTest test` | |
||||
|
|
||||
|
## Build Rules |
||||
|
|
||||
|
- 每个 Batch 按 TDD 先写失败测试,再实现最小行为,不得跳过测试。 |
||||
|
- 只修改范围内文件;发现需要修改已验收接口或历史迁移时立即停止并触发 Rewind。 |
||||
|
- 迁移和种子数据必须可在 H2(`MODE=MySQL`)与真实 MySQL 执行,不依赖外部文件路径。 |
||||
|
- 所有排序、匹配和建议必须确定性;禁止随机、当前时间或请求次数影响结果。 |
||||
|
- 任何新增日志不得包含个人技能 JSON、Token、手机号、邮箱或简历文本。 |
||||
|
- OpenAPI 注解、示例和实际响应必须一致,不能只改文档不改运行时行为。 |
||||
|
|
||||
|
## Review Gates |
||||
|
|
||||
|
- **DP-3**:本契约必须由用户明确批准并记录后,才能开始任何实现。 |
||||
|
- **Batch Review**:B1、B2、B3 每个 Batch 完成后由 `code-reviewer` 按规范符合性和代码质量评审,记录 `pass` 或 `fail`。 |
||||
|
- **DP-4**:批准契约后由 `build-executor` 给出执行模式与 wave 计划,用户确认后开始执行。 |
||||
|
- **Verification Gate**:实现完成后执行全量测试、OpenAPI 校验和必要的 MySQL/Docker 黑盒验证。 |
||||
|
- **DP-7**:由 `release-archivist` 完成最终验证与关闭,不在本契约内自动完成。 |
||||
|
|
||||
|
## Rewind Conditions |
||||
|
|
||||
|
出现以下任一情况必须停止执行并回到规划阶段刷新工件和本契约: |
||||
|
|
||||
|
- 需要新增、删除或改变用户可见接口、筛选参数、权限或响应语义。 |
||||
|
- TOP10、匹配度分母、排序规则或空数据处理发生改变。 |
||||
|
- 需要引入 AI、课程平台、实时岗位热度或后台维护接口。 |
||||
|
- 需要导入全量词库或扩大首版行业/职位范围。 |
||||
|
- 需要新增除 ESCO、O*NET 之外的其他技能来源。 |
||||
|
- V35/V36 被其他变更占用,或历史迁移需要修改。 |
||||
|
- ESCO/O*NET 许可、中文映射策略或数据来源发生实质变化。 |
||||
|
- 发现某个已批准 `MUST`/`SHALL` 无法映射到测试或执行批次。 |
||||
|
|
||||
|
## DP-3 Approval Gate |
||||
|
|
||||
|
执行者确认:本契约完整覆盖已批准的 14 条必需行为,无未映射需求,B1→B2→B3 依赖清晰,所有测试义务均有证据命令。 |
||||
|
|
||||
|
**批准方式**:用户明确回复批准本执行契约后,记录 `dp_3_result`、`dp_3_timestamp`,再执行 `specifying → bridging → approved-for-build` 状态转换。DP-3 记录前禁止进入 `build-executor`。 |
||||
@ -0,0 +1,59 @@ |
|||||
|
# N03 行业技能词云与学习路径接口 |
||||
|
|
||||
|
## Why |
||||
|
|
||||
|
当前系统已经具备学生资料、技能标签、岗位匹配和求职进度能力,但学生无法从行业与职位的视角看到“市场在要求哪些技能”,也无法知道自己相对热门技能 TOP10 的掌握程度,更无法直接得到按优先级排列的学习补缺清单。仓库截至 V34 没有技能词云、行业技能热度或个人学习路径实现,现有 `student_profiles.skills_json` 与 `jobs.requirements` 也尚未被统一为可查询的技能分类体系。 |
||||
|
|
||||
|
本变更通过引入 ESCO + O*NET 的开源知识底座,为“行业 → 职位 → 技能分类 → 技能”建立可版本化数据,并为已登录学生提供三个确定性接口:查看词云与 TOP10、计算个人技能匹配度、生成缺失技能学习路径。 |
||||
|
|
||||
|
## What Changes |
||||
|
|
||||
|
- 新增 Flyway 词库表,保存行业、职位、技能分类、技能、别名、职位技能关系、O*NET Importance 热度及数据来源版本。 |
||||
|
- 首版导入互联网、金融、教育行业及代表性职位;中文名称、别名和细粒度技能分类由项目维护映射。 |
||||
|
- 新增 `GET /api/v1/skills/word-cloud`:按行业、职位、可选技能分类返回全部技能,按热度降序排列。 |
||||
|
- 新增 `GET /api/v1/skills/match`:用当前学生技能命中当前筛选结果 TOP10,返回匹配度、命中技能和缺失技能。 |
||||
|
- 新增 `GET /api/v1/skills/learning-path`:对缺失 TOP10 技能按热度生成优先级和通用学习建议。 |
||||
|
- 三个接口仅允许已登录 STUDENT 访问,统一使用现有 JWT、角色校验和 `ApiResponse` 规范。 |
||||
|
- 更新 OpenAPI 契约、前端联调文档和词库来源说明。 |
||||
|
|
||||
|
## Scope |
||||
|
|
||||
|
### In Scope |
||||
|
|
||||
|
- ESCO 与 O*NET 的代表性数据导入、中文映射、来源版本和许可归属。 |
||||
|
- 行业、职位、技能分类、技能、别名、热度及职位技能关系查询。 |
||||
|
- 当前筛选范围内的完整技能词云,以及可由前端直接截取的 TOP10。 |
||||
|
- 个人 `skills_json` 与 TOP10 的别名归一化匹配。 |
||||
|
- 匹配度按 `命中数 / min(10, 实际技能数)` 计算,实际技能数为 0 时返回 0。 |
||||
|
- 缺失技能按同一热度排序生成优先级 1、2、3…。 |
||||
|
- 按技能分类生成通用学习建议,不依赖 AI 或外部服务。 |
||||
|
- 单元测试、MockMvc 测试、H2 持久化测试和 OpenAPI 静态校验。 |
||||
|
|
||||
|
### Out of Scope |
||||
|
|
||||
|
- 平台岗位 `jobId`、岗位发布频率或实时文本抽取参与热度计算。 |
||||
|
- ESCO/O*NET 全量导入、自动翻译全量技能名称、在线同步开源词库。 |
||||
|
- 后台词库维护页面、运营导入工具或运行时更新接口。 |
||||
|
- AI 生成课程大纲、课程链接、讲师、学习时长或培训平台对接。 |
||||
|
- 保存个人匹配历史、学习进度、完成状态或提醒任务。 |
||||
|
- 企业和管理员查看学生个人匹配结果。 |
||||
|
- 前端页面、交互设计和可视化实现。 |
||||
|
|
||||
|
## Impact |
||||
|
|
||||
|
- `mydreamjob-server/src/main/resources/db/migration`:新增词库结构迁移和代表性种子数据。 |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob`:新增技能词云领域包、实体、Repository、Service、DTO 和 Controller,并调整安全配置。 |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob`:新增查询排序、匹配、学习路径和权限场景测试。 |
||||
|
- `docs/openapi-rc.json`、`docs/frontend-integration.md`:登记三个接口和响应示例。 |
||||
|
- 新增词库来源与版本说明文档,保留 ESCO/O*NET 的许可和数据版本。 |
||||
|
- 不修改已有岗位匹配、候选人评分、求职进度等已验收接口语义。 |
||||
|
|
||||
|
## Proof of Completion |
||||
|
|
||||
|
- Flyway 在 MySQL 模式测试库成功创建词库表并导入代表性数据。 |
||||
|
- 三个接口返回统一 `ApiResponse`,匿名访问为 401,非 STUDENT 为 403。 |
||||
|
- 词云结果按 O*NET Importance 降序;无权重技能保留且排在有权重技能之后;同权重排序稳定。 |
||||
|
- TOP10 与词云使用同一排序结果,匹配度和学习路径复用该结果。 |
||||
|
- 匹配度覆盖 0、少于 10 项、恰好 10 项、全部命中和部分命中场景。 |
||||
|
- 学习路径优先级连续,且只包含未命中的 TOP10 技能;无缺口时返回空列表。 |
||||
|
- `mvn test`、`mvn clean verify` 和 `python docs/verify_openapi.py` 全部通过。 |
||||
@ -0,0 +1,84 @@ |
|||||
|
# Learning Path Specification |
||||
|
|
||||
|
## ADDED Requirements |
||||
|
|
||||
|
### Requirement: Learning path consumes unmatched TOP10 skills |
||||
|
|
||||
|
学习路径接口 MUST 复用个人技能匹配结果,并只包含当前 TOP10 中未命中的技能;不得包含已命中技能或 TOP10 之外的技能。 |
||||
|
|
||||
|
#### Scenario: Student has skill gaps |
||||
|
|
||||
|
- **WHEN** 当前 TOP10 中部分技能未命中 |
||||
|
- **THEN** 学习路径 MUST 包含全部未命中技能且不含已命中技能 |
||||
|
|
||||
|
#### Scenario: Student has no skill gaps |
||||
|
|
||||
|
- **WHEN** 当前 TOP10 全部命中 |
||||
|
- **THEN** 学习路径 MUST 返回空列表和总数 0 |
||||
|
|
||||
|
#### Scenario: Student has no matching skill evidence |
||||
|
|
||||
|
- **WHEN** 学生没有技能数据 |
||||
|
- **THEN** 学习路径 MUST 包含当前 TOP10 的全部技能 |
||||
|
|
||||
|
### Requirement: Learning priority follows heat order |
||||
|
|
||||
|
学习路径项 MUST 按与 TOP10 相同的热度顺序排列,并从 1 开始连续编号;空热度技能继续遵守词云的末位排序规则。 |
||||
|
|
||||
|
#### Scenario: Multiple missing skills are ordered |
||||
|
|
||||
|
- **WHEN** 缺失技能包含多个热度不同的技能 |
||||
|
- **THEN** 热度最高的技能 MUST 获得优先级 1,后续技能依次编号 |
||||
|
|
||||
|
#### Scenario: Missing skills have equal heat |
||||
|
|
||||
|
- **WHEN** 缺失技能热度相同 |
||||
|
- **THEN** 优先级 MUST 沿用词云的稳定次级排序,重复请求结果一致 |
||||
|
|
||||
|
#### Scenario: Only one skill is missing |
||||
|
|
||||
|
- **WHEN** 当前 TOP10 只有一项未命中 |
||||
|
- **THEN** 该技能 MUST 返回优先级 1 |
||||
|
|
||||
|
### Requirement: Learning suggestions are deterministic and industry-neutral |
||||
|
|
||||
|
每项学习建议 MUST 由后端按技能分类的通用模板生成,不调用 AI、知识库问答或外部课程服务;建议不得包含课程链接、讲师、价格、学习时长或完成度。 |
||||
|
|
||||
|
#### Scenario: Programming language gap is returned |
||||
|
|
||||
|
- **WHEN** 缺失技能属于编程语言分类 |
||||
|
- **THEN** 建议 MUST 描述基础语法学习和编码练习方向 |
||||
|
|
||||
|
#### Scenario: Framework gap is returned |
||||
|
|
||||
|
- **WHEN** 缺失技能属于框架分类 |
||||
|
- **THEN** 建议 MUST 描述核心概念学习和小型项目实践方向 |
||||
|
|
||||
|
#### Scenario: Tool gap is returned |
||||
|
|
||||
|
- **WHEN** 缺失技能属于工具分类 |
||||
|
- **THEN** 建议 MUST 描述安装配置和工程实践方向 |
||||
|
|
||||
|
#### Scenario: Unknown category is returned |
||||
|
|
||||
|
- **WHEN** 缺失技能无法匹配特定模板分类 |
||||
|
- **THEN** 系统 MUST 返回一条行业无关的通用学习建议 |
||||
|
|
||||
|
### Requirement: Learning path response is actionable and minimal |
||||
|
|
||||
|
每个学习路径项 MUST 返回技能 ID、技能名称、技能分类、优先级和简化学习建议;响应 MUST 不包含个人隐私、简历原文或模型推理内容。 |
||||
|
|
||||
|
#### Scenario: A learning item is generated |
||||
|
|
||||
|
- **WHEN** 存在未命中技能 |
||||
|
- **THEN** 每个路径项 MUST 同时具备技能身份、分类、连续优先级和建议文本 |
||||
|
|
||||
|
#### Scenario: Repeated request is made |
||||
|
|
||||
|
- **WHEN** 学生资料、词库数据和筛选条件均未变化 |
||||
|
- **THEN** 接口 MUST 返回相同顺序、相同优先级和相同建议 |
||||
|
|
||||
|
#### Scenario: Response contains no private profile data |
||||
|
|
||||
|
- **WHEN** 学习路径成功返回 |
||||
|
- **THEN** 响应 MUST 不含手机号、邮箱、简历文件、原始 `skills_json` 或其他无关个人数据 |
||||
@ -0,0 +1,98 @@ |
|||||
|
# Skill Cloud Specification |
||||
|
|
||||
|
## ADDED Requirements |
||||
|
|
||||
|
### Requirement: STUDENT-only skill cloud access |
||||
|
|
||||
|
系统 MUST 只允许已登录 STUDENT 调用技能词云接口,并从 JWT 解析当前用户,不接受客户端传入学生身份参数。 |
||||
|
|
||||
|
#### Scenario: Anonymous request is rejected |
||||
|
|
||||
|
- **WHEN** 未携带有效 JWT 请求 `GET /api/v1/skills/word-cloud` |
||||
|
- **THEN** 系统 MUST 返回 401,且不得返回任何词库数据 |
||||
|
|
||||
|
#### Scenario: Company or administrator request is rejected |
||||
|
|
||||
|
- **WHEN** COMPANY 或 ADMIN 用户携带有效 JWT 请求技能词云 |
||||
|
- **THEN** 系统 MUST 返回 403,且不得返回任何词库数据 |
||||
|
|
||||
|
#### Scenario: Student request is accepted |
||||
|
|
||||
|
- **WHEN** STUDENT 用户携带有效 JWT 请求技能词云 |
||||
|
- **THEN** 系统 MUST 按筛选条件返回词库数据 |
||||
|
|
||||
|
### Requirement: Skill cloud filter validation |
||||
|
|
||||
|
接口 MUST 要求 `industryId` 和 `occupationId` 为必填正整数,`categoryId` 为可选正整数;行业与职位必须存在且互为关联,技能分类存在时必须属于该职位。 |
||||
|
|
||||
|
#### Scenario: Required filters are missing |
||||
|
|
||||
|
- **WHEN** 请求未传 `industryId` 或 `occupationId` |
||||
|
- **THEN** 系统 MUST 返回 400,并说明缺失的筛选参数 |
||||
|
|
||||
|
#### Scenario: Occupation does not belong to industry |
||||
|
|
||||
|
- **WHEN** 请求传入存在但不属于目标行业的职位 |
||||
|
- **THEN** 系统 MUST 返回 400,且不得返回技能数据 |
||||
|
|
||||
|
#### Scenario: Category does not belong to occupation |
||||
|
|
||||
|
- **WHEN** 请求传入存在但不属于目标职位的技能分类 |
||||
|
- **THEN** 系统 MUST 返回 400,且不得返回技能数据 |
||||
|
|
||||
|
#### Scenario: Category is omitted |
||||
|
|
||||
|
- **WHEN** 请求只传行业和职位 |
||||
|
- **THEN** 系统 MUST 返回该职位下所有技能分类的技能 |
||||
|
|
||||
|
### Requirement: Complete ranked skill cloud |
||||
|
|
||||
|
系统 MUST 返回当前筛选范围内全部技能,并按 `heat` 降序排列;`heat` 为空时排在非空值之后。同 `heat` 或同为空值时,系统 MUST 使用稳定的中文技能名称和技能 ID 作为次级排序条件。 |
||||
|
|
||||
|
#### Scenario: Skills are ordered by heat |
||||
|
|
||||
|
- **WHEN** 筛选结果存在多个带 O*NET Importance 的技能 |
||||
|
- **THEN** 返回列表 MUST 按数值从高到低排列 |
||||
|
|
||||
|
#### Scenario: Skills without heat are retained |
||||
|
|
||||
|
- **WHEN** 筛选结果包含无 O*NET Importance 的 ESCO 技能 |
||||
|
- **THEN** 这些技能 MUST 保留在列表中,`heat` 为 null,并排在所有非 null 热度技能之后 |
||||
|
|
||||
|
#### Scenario: Equal heat has deterministic order |
||||
|
|
||||
|
- **WHEN** 两个技能具有相同 `heat` |
||||
|
- **THEN** 系统 MUST 按稳定次级规则返回一致顺序,重复请求不得随机变化 |
||||
|
|
||||
|
#### Scenario: Empty scope returns empty list |
||||
|
|
||||
|
- **WHEN** 合法筛选范围内没有技能 |
||||
|
- **THEN** 系统 MUST 返回成功响应、空技能列表和总数 0 |
||||
|
|
||||
|
### Requirement: TOP10 is derived from the skill cloud |
||||
|
|
||||
|
系统 MUST NOT 为 TOP10 维护独立排序或独立数据源;前端必须能够直接使用词云返回列表的前十条得到 TOP10。 |
||||
|
|
||||
|
#### Scenario: Scope has at least ten skills |
||||
|
|
||||
|
- **WHEN** 词云结果包含至少十条技能 |
||||
|
- **THEN** 前十条 MUST 与同一筛选条件下的热门技能 TOP10 一致 |
||||
|
|
||||
|
#### Scenario: Scope has fewer than ten skills |
||||
|
|
||||
|
- **WHEN** 词云结果少于十条技能 |
||||
|
- **THEN** 全部结果 MUST 构成实际 TOP10 集合 |
||||
|
|
||||
|
### Requirement: Skill cloud response exposes source-linked metadata |
||||
|
|
||||
|
每个技能项 MUST 至少返回技能 ID、中文名称、技能分类 ID、技能分类名称、`heat` 和稳定排名。技能仍可通过 ID 追溯至 ESCO/O*NET 来源。 |
||||
|
|
||||
|
#### Scenario: Skill item is returned |
||||
|
|
||||
|
- **WHEN** 查询返回任一技能 |
||||
|
- **THEN** 响应 MUST 包含该技能的身份、分类、热度和排名字段 |
||||
|
|
||||
|
#### Scenario: Response is wrapped consistently |
||||
|
|
||||
|
- **WHEN** 请求成功 |
||||
|
- **THEN** 响应 MUST 使用项目统一 `ApiResponse` 结构,并在 `data` 中返回筛选条件和技能列表 |
||||
@ -0,0 +1,98 @@ |
|||||
|
# Skill Match Specification |
||||
|
|
||||
|
## ADDED Requirements |
||||
|
|
||||
|
### Requirement: Personal matching uses the current ranked TOP10 |
||||
|
|
||||
|
个人技能匹配接口 MUST 复用技能词云相同的筛选参数、数据范围和热度排序;匹配集合 MUST 是当前筛选结果的前 `min(10, 实际技能数)` 项。 |
||||
|
|
||||
|
#### Scenario: Filtered result has at least ten skills |
||||
|
|
||||
|
- **WHEN** 当前筛选返回十条或更多技能 |
||||
|
- **THEN** 匹配集合 MUST 恰好包含热度最高的十条技能 |
||||
|
|
||||
|
#### Scenario: Filtered result has fewer than ten skills |
||||
|
|
||||
|
- **WHEN** 当前筛选返回六条技能 |
||||
|
- **THEN** 匹配集合 MUST 包含全部六条技能,分母 MUST 为 6 |
||||
|
|
||||
|
#### Scenario: Filtered result is empty |
||||
|
|
||||
|
- **WHEN** 当前合法筛选范围内没有技能 |
||||
|
- **THEN** 匹配度 MUST 返回 0,命中和缺失列表 MUST 均为空 |
||||
|
|
||||
|
### Requirement: Student skills are read from the authenticated profile |
||||
|
|
||||
|
系统 MUST 从当前登录 STUDENT 的 `student_profiles.skills_json` 读取技能;不得接受客户端另行提交的技能列表作为个人匹配依据。 |
||||
|
|
||||
|
#### Scenario: Student profile has valid skills |
||||
|
|
||||
|
- **WHEN** 当前学生存在合法技能数组 |
||||
|
- **THEN** 系统 MUST 使用该数组计算匹配结果 |
||||
|
|
||||
|
#### Scenario: Student profile is absent or has no skills |
||||
|
|
||||
|
- **WHEN** 当前学生没有资料记录、技能字段为空或技能数组为空 |
||||
|
- **THEN** 匹配度 MUST 返回 0,命中列表为空,缺失列表为当前 TOP10 |
||||
|
|
||||
|
#### Scenario: Skills JSON is malformed |
||||
|
|
||||
|
- **WHEN** 当前学生技能字段无法解析为字符串数组 |
||||
|
- **THEN** 系统 MUST 按无技能处理,不得导致 500 响应或泄露原始字段内容 |
||||
|
|
||||
|
### Requirement: Skill matching normalizes aliases |
||||
|
|
||||
|
系统 MUST 使用技能标准和别名进行匹配,归一化至少覆盖首尾空白、大小写、连续空格和常见标点差异;每个标准技能命中一次即可。 |
||||
|
|
||||
|
#### Scenario: Alias matches canonical skill |
||||
|
|
||||
|
- **WHEN** 学生技能为 `SpringBoot`,TOP10 标准技能为 `Spring Boot` 且存在对应别名 |
||||
|
- **THEN** 系统 MUST 将该技能计为命中 |
||||
|
|
||||
|
#### Scenario: Case and spacing differ |
||||
|
|
||||
|
- **WHEN** 学生技能与标准技能的字母大小写或空格数量不同 |
||||
|
- **THEN** 系统 MUST 在归一化后判断为同一技能 |
||||
|
|
||||
|
#### Scenario: Duplicate student skills exist |
||||
|
|
||||
|
- **WHEN** 学生技能数组包含多个指向同一标准技能的别名 |
||||
|
- **THEN** 系统 MUST 只计一次命中 |
||||
|
|
||||
|
### Requirement: Match score uses the actual TOP10 size |
||||
|
|
||||
|
匹配度 MUST 使用公式 `round(命中数 × 100 / min(10, 实际技能数))` 计算,并返回 0 到 100 的整数。实际技能数为 0 时,匹配度 MUST 为 0。 |
||||
|
|
||||
|
#### Scenario: All skills match |
||||
|
|
||||
|
- **WHEN** 当前 TOP10 有 8 项,学生命中全部 8 项 |
||||
|
- **THEN** 匹配度 MUST 返回 100 |
||||
|
|
||||
|
#### Scenario: Partial match is rounded |
||||
|
|
||||
|
- **WHEN** 当前 TOP10 有 3 项,学生命中 1 项 |
||||
|
- **THEN** 匹配度 MUST 返回 33 |
||||
|
|
||||
|
#### Scenario: No skill matches |
||||
|
|
||||
|
- **WHEN** 当前 TOP10 非空且学生没有命中任何技能 |
||||
|
- **THEN** 匹配度 MUST 返回 0 |
||||
|
|
||||
|
### Requirement: Match response separates matched and missing skills |
||||
|
|
||||
|
响应 MUST 返回匹配度、命中数量、实际匹配集合大小、命中技能列表和缺失技能列表;缺失技能 MUST 保持 TOP10 的热度排序。 |
||||
|
|
||||
|
#### Scenario: Some skills are matched |
||||
|
|
||||
|
- **WHEN** 学生命中 TOP10 的部分技能 |
||||
|
- **THEN** 命中列表和缺失列表 MUST 分别返回对应技能,且并集等于 TOP10 |
||||
|
|
||||
|
#### Scenario: All skills are matched |
||||
|
|
||||
|
- **WHEN** 学生命中 TOP10 的全部技能 |
||||
|
- **THEN** 缺失技能列表 MUST 为空,匹配度 MUST 为 100 |
||||
|
|
||||
|
#### Scenario: No skills are matched |
||||
|
|
||||
|
- **WHEN** 学生没有命中 TOP10 的任何技能 |
||||
|
- **THEN** 命中技能列表 MUST 为空,缺失列表 MUST 等于 TOP10 |
||||
@ -0,0 +1,38 @@ |
|||||
|
# 实施任务 |
||||
|
|
||||
|
## 交付与证明映射 |
||||
|
|
||||
|
| 需求域 | 规划来源 | 实现任务 | 主要证明 | |
||||
|
| --- | --- | --- | --- | |
||||
|
| 词库来源与覆盖 | proposal、design | T1、T2、T3 | 来源清单、中文名检查、Flyway 执行成功 | |
||||
|
| 词云筛选与排序 | skill-cloud spec | T4、T5 | Repository/Service 单测、MockMvc 场景 | |
||||
|
| TOP10 派生与一致性 | skill-cloud、skill-match spec | T5、T7、T8 | 集成测试断言同一排序结果 | |
||||
|
| 个人技能匹配 | skill-match spec | T6、T8 | 别名、分母、空数据和部分命中单测 | |
||||
|
| 学习路径与建议 | learning-path spec | T7、T8 | 优先级连续、模板建议和空列表测试 | |
||||
|
| 权限与隐私 | 三份 spec、design | T8、T10 | 401/403、响应脱敏和日志检查 | |
||||
|
| 文档与契约 | proposal、design | T1、T10 | `docs/verify_openapi.py`、接口文档审查 | |
||||
|
|
||||
|
## 任务 |
||||
|
|
||||
|
- [ ] 1. 固化 ESCO + O*NET 代表性数据清单:在 `changes/n03-skill-cloud/data/source-manifest.md` 和 `docs/skill-taxonomy-sources.md` 记录行业、职位、技能范围、源版本、许可、归属 URL、中文映射负责人和导入规则。产出:互联网、金融、教育及代表性职位的可审查数据边界。依赖:无。证据:人工审查清单,所有入选技能均有稳定来源 ID 和中文标准名。 |
||||
|
- [ ] 2. 新增 Flyway 词库结构迁移:创建 `mydreamjob-server/src/main/resources/db/migration/V35__skill_cloud_taxonomy.sql`,实现七张词库表、外键、唯一约束、查询索引,以及 `occupation_skills.importance_source_code`、`importance_element_id`、`importance_is_proxy` 来源追溯字段,不修改 V1–V34。产出:H2/MySQL 均可创建词库结构。依赖:T1。证据:`mvn -Dtest=SkillTaxonomyMigrationTest test`。 |
||||
|
- [ ] 3. 导入代表性种子数据:创建 `mydreamjob-server/src/main/resources/db/migration/V36__skill_cloud_seed.sql`,导入三类行业、代表性职位、全部已声明技能分类、ESCO 主来源技能、O*NET `software_skills` 补充技术、别名和 O*NET Importance;每条非空热度必须保存真实 SOC、Element ID 和代理标记,无映射时 `importance` 留空。产出:种子数据可重复执行迁移并满足中文名约束。依赖:T2。证据:迁移测试断言数量、中文名、分类覆盖、ESCO 职位来源、47 条 O*NET 技术来源和全部非空 Importance 与版本化证据快照一致。 |
||||
|
- [ ] 4. 建立词库领域与查询仓储:在 `mydreamjob-server/src/main/java/com/mydreamjob/skill/` 新增实体、Repository 和查询投影,覆盖行业—职位—分类校验及技能关系查询。产出:能按 `industryId`、`occupationId`、可选 `categoryId` 读取技能。依赖:T2、T3。证据:`mvn -Dtest=SkillTaxonomyRepositoryTest test`。 |
||||
|
- [ ] 5. 实现共享词云排名服务与 DTO:在 `mydreamjob-server/src/main/java/com/mydreamjob/skill/` 实现统一筛选、`heat DESC, nameZh ASC, skillId ASC` 排序和完整列表输出;返回 `rank`、技能身份、分类、`heat`、来源字段。产出:词云和 TOP10 使用同一个事实来源。依赖:T4。证据:`mvn -Dtest=SkillCloudQueryServiceTest test`,覆盖空热度、同热度、空范围和分类缺省。 |
||||
|
- [ ] 6. 实现个人技能匹配服务:新增 `SkillMatchService`、别名归一化器和匹配 DTO,只从 JWT 对应学生的 `skills_json` 读取技能;按 `min(10, actualSize)` 截取并计算整数匹配度。产出:命中、缺失、分母和异常 JSON 行为符合规范。依赖:T5。证据:`mvn -Dtest=SkillMatchServiceTest test`,覆盖 0、1/3=33、全命中、少技能和重复别名。 |
||||
|
- [ ] 7. 实现学习路径生成服务:新增 `SkillLearningPathService` 和分类模板,按缺失技能顺序生成连续优先级与通用建议;不接 AI 或外部资源。产出:无缺口返回空列表,有缺口返回最小学路径。依赖:T6。证据:`mvn -Dtest=SkillLearningPathServiceTest test`。 |
||||
|
- [ ] 8. 暴露三个 STUDENT 接口并登记权限:新增 `SkillCloudController`,实现 `GET /skills/word-cloud`、`GET /skills/match`、`GET /skills/learning-path`,在 `SecurityConfig` 中加入 `/skills/**` 的登录与 STUDENT 校验,并补充 OpenAPI 注解。产出:三个 API 只接受 STUDENT,响应统一为 `ApiResponse`。依赖:T5、T6、T7。证据:`mvn -Dtest=SkillCloudControllerTest test`,覆盖匿名 401、COMPANY/ADMIN 403、参数非法 400 和学生成功 200。 |
||||
|
- [ ] 9. 补齐持久化与端到端回归测试:在 `mydreamjob-server/src/test/java/com/mydreamjob/skill/` 增加 H2 持久化、排序一致性、匹配与学习路径复用同一 TOP10 的集成测试,并验证响应不含原始 `skills_json`。产出:三个接口的边界与一致性有自动化证明。依赖:T3、T8。证据:`mvn test`。 |
||||
|
- [ ] 10. 更新接口与来源文档并执行发布级验证:新增 `docs/skill-cloud-api.md`,更新 `docs/frontend-integration.md`、`docs/openapi-rc.json` 和词库来源说明,明确 TOP10 由前端截取、参数规则、响应字段、无 AI 和许可归属。依赖:T8、T9。产出:静态契约、运行时行为和文档一致。证据:`python docs\verify_openapi.py`、`mvn clean verify`。 |
||||
|
|
||||
|
## 实施顺序 |
||||
|
|
||||
|
T1 → T2 → T3 → T4 → T5 → T6 → T7 → T8 → T9 → T10。 |
||||
|
|
||||
|
## 退出条件 |
||||
|
|
||||
|
- 所有任务完成且证据命令通过。 |
||||
|
- 三份 spec 的场景全部有对应自动化或明确人工契约验证。 |
||||
|
- 词库来源版本和许可可追溯,未修改历史迁移。 |
||||
|
- 未实现非目标范围;没有课程、AI、后台维护或个人进度持久化。 |
||||
|
- OpenAPI 静态契约与运行时接口一致,且 STUDENT 权限边界可验证。 |
||||
@ -0,0 +1,50 @@ |
|||||
|
version: 1 |
||||
|
state: closing |
||||
|
workflow: full |
||||
|
change_name: n04-onboard |
||||
|
artifact_language: zh-CN |
||||
|
dp_0_confirmed: true |
||||
|
dp_0_result: confirmed |
||||
|
dp_0_timestamp: 2026-09-16T07:10:12Z |
||||
|
dp_0_decisions: | |
||||
|
变更名称:n04-onboard;一句话意图:为学生提供标准项与企业自定义项组合的入职清单,并基于企业资料、员工评价和公司维护内容提供公司知识介绍。 |
||||
|
工作流:full;选择原因:新增业务模块、Flyway 表结构、九个 API、角色权限、幂等与并发控制、跨模块评价聚合。 |
||||
|
约束:仅登录用户可访问;学生端读取/更新本人完成状态,企业端仅维护本企业自定义清单与知识内容;通用清单通过 Flyway 种子数据提供;企业未配置自定义项时回退通用清单;公司知识信息不足时返回企业基础信息;不调用 AI。 |
||||
|
相关优化:保持聚焦,不纳入 ADMIN 模板治理、内容审核发布、版本回滚、通知提醒、职场内容推荐、试用期提醒、岗位或 Offer 个性化清单、前端开发。 |
||||
|
沟通方式:四类规划工件一次生成,完成验证后统一进入 DP-2 审查。 |
||||
|
artifact_language=zh-CN |
||||
|
dp_1_result: confirmed |
||||
|
dp_1_timestamp: 2026-09-16T07:10:12Z |
||||
|
artifacts: |
||||
|
order: [proposal, specs, design, tasks] |
||||
|
skip: [] |
||||
|
dp_2_result: approved: proposal, two delta specs, design, and tasks validated |
||||
|
dp_2_confirmed: true |
||||
|
dp_2_timestamp: 2026-09-16T07:45:22Z |
||||
|
contract_hash: 79e718e36a2fd349a8c26dfd080e74a1844c01a6e52363613414694bc75a74d1 |
||||
|
dp_3_result: approved: execution contract B1-B3; all 12 approved requirements mapped |
||||
|
dp_3_confirmed: true |
||||
|
dp_3_timestamp: 2026-09-16T07:55:53Z |
||||
|
execution_mode: sdd |
||||
|
plan_revision: 1 |
||||
|
dp_4_result: approved: SDD selected; W1 serial T1-T2, W2 serial T3-T5 depends W1, W3 serial T6-T8 depends W2 |
||||
|
dp_4_confirmed: true |
||||
|
dp_4_timestamp: 2026-09-16T07:58:31Z |
||||
|
batches_completed: 3 |
||||
|
execution_paused: false |
||||
|
paused_at: 2026-09-16T09:51:43Z |
||||
|
paused_reason: user requested pause for the day; T7 tests are complete but independent T7 review and T8 remain pending |
||||
|
resume_from: T7-review |
||||
|
resume_mode: manual |
||||
|
resumed_at: 2026-09-17T01:17:47Z |
||||
|
resume_count: 1 |
||||
|
verification_blocked: false |
||||
|
verification_outcome: conditional-pass-with-formal-waiver |
||||
|
dp_6_result: pass: N04 96/96 tests, OpenAPI 135 paths / 9 onboarding operations, package pass; 1492-test full suite has 2 failures and 9 errors in pre-existing non-N04 tests formally waived; real MySQL/Docker acceptance remains a pre-release gate |
||||
|
dp_6_timestamp: 2026-09-17T02:47:50Z |
||||
|
test_result: pass: scoped verification with formal waiver |
||||
|
spec_sync_result: manual publication passed: 12 requirements across 2 capabilities |
||||
|
spec_sync_timestamp: 2026-09-17T02:48:23Z |
||||
|
dp_7_result: confirmed: archive N04 and manually merge feat/n04-onboard to master |
||||
|
dp_7_timestamp: 2026-09-17T08:03:00Z |
||||
|
closed_at: 2026-09-17T08:03:00Z |
||||
@ -0,0 +1,26 @@ |
|||||
|
plan_revision: 1 |
||||
|
selected_mode: sdd |
||||
|
contract_hash: 79e718e36a2fd349a8c26dfd080e74a1844c01a6e52363613414694bc75a74d1 |
||||
|
created_at: 2026-09-16T07:58:31Z |
||||
|
current: verification |
||||
|
waves: |
||||
|
- id: W1 |
||||
|
strategy: serial |
||||
|
tasks: [T1, T2] |
||||
|
depends_on: [] |
||||
|
state: passed |
||||
|
receipt: .superpowers/sdd/reviews/W1-wave.md |
||||
|
- id: W2 |
||||
|
strategy: serial |
||||
|
tasks: [T3, T4, T5] |
||||
|
depends_on: [W1] |
||||
|
state: passed |
||||
|
receipt: .superpowers/sdd/reviews/W2-wave.md |
||||
|
- id: W3 |
||||
|
strategy: serial |
||||
|
tasks: [T6, T7, T8] |
||||
|
depends_on: [W2] |
||||
|
state: passed_with_external_blocker |
||||
|
receipt: .superpowers/sdd/reviews/W3-wave.md |
||||
|
verification_blocked: true |
||||
|
closure_requires: external full-suite repair or waiver; real MySQL 8.4 / Docker acceptance |
||||
@ -0,0 +1,26 @@ |
|||||
|
# N04 SDD Progress Ledger |
||||
|
|
||||
|
- 2026-09-16T07:58:31Z Plan revision 1 created for mode SDD. |
||||
|
- W1 eligible: T1 -> T2 (serial, no prerequisite). |
||||
|
- W2 blocked on W1: T3 -> T4 -> T5. |
||||
|
- W3 blocked on W2: T6 -> T7 -> T8. |
||||
|
- 2026-09-16T08:10:18Z Task T1: complete (commit 7fe96cc, review W1-T1 PASS, RED->GREEN, 2 tests). |
||||
|
- 2026-09-16T08:22:24Z Task T2: complete (commit ef4a9b8, review W1-T2 PASS, RED->GREEN, 5 tests). |
||||
|
- 2026-09-16T08:24:45Z W1 wave review PASS: 7 tests; W1 receipt accepted; W2/T3 eligible. |
||||
|
- 2026-09-16T08:38:07Z Task T3: complete (commit 037ddc7, review W2-T3 PASS, RED->GREEN, 15 tests). |
||||
|
- 2026-09-16T08:50:22Z Task T4: complete (commit 7370e51, review W2-T4 PASS, 22 tests; concurrent-create race deferred to T6). |
||||
|
- 2026-09-16T09:06:51Z Task T5: complete (commit a5730a7, review W2-T5 PASS, 22 tests). |
||||
|
- 2026-09-16T09:10:27Z W2 wave review PASS: 66 tests; W2 receipt accepted; W3/T6 eligible with concurrency obligations. |
||||
|
- 2026-09-16T09:27:35Z Task T6: failed attempt 1/3 - review W3-T6 FAIL; MySQL REPEATABLE READ concurrent 49->50 limit check can write item 51. |
||||
|
- 2026-09-16T09:40:55Z Task T6: complete after focused repair (commit 97b3b16; W3-T6-rereview PASS; 22 focused tests, 85 onboarding tests). |
||||
|
- 2026-09-16T09:51:11Z Task T7: complete (no commit per instruction; RED->GREEN coverage added; OnboardingEndToEndIntegrationTest 7 tests; 2-review/tag/HIDDEN focused gaps; 96 onboarding tests pass; randomized E2E order pass). |
||||
|
- 2026-09-16T09:51:43Z Execution paused by user for the day. T7 tests complete (96 onboarding tests green); independent T7 review and T8 pending. Manual resume from T7-review only. |
||||
|
- 2026-09-17T01:17:47Z Manual resume accepted. W3 resumes at T7 independent review; T6 remains passed and T8 remains pending. |
||||
|
- 2026-09-17T01:21:54Z Task T7: review W3-T7 PASS; 30 focused tests, 96 onboarding regressions, randomized E2E pass. |
||||
|
- 2026-09-17T09:33:54Z Task T8: documentation implementation complete; after adding missing onboarding assertions, verifier RED was `Missing onboarding path: /onboarding/companies/{companyId}/checklist`; static OpenAPI now 135 paths / 172 operations and contains 9 onboarding operations / 6 paths / 20 schemas; `python docs/verify_openapi.py` PASS; N04 focused regression PASS (96 tests); `mvn clean verify` FAIL (1492 tests, 2 failures, 9 errors) in existing CareerProgress/JobReviewDimension/LocalResumeStorage tests unrelated to T8; `mvn -DskipTests package` PASS; no commit; independent T8/W3 review pending. |
||||
|
- 2026-09-17T01:41:13Z Task T8: failed attempt 1/3 - review W3-T8 FAIL; OpenAPI knowledge example lacks version and create request schema omits runtime defaults. |
||||
|
- 2026-09-17T01:45:47Z Task T8: complete after focused repair (commit 310d931; W3-T8-rereview PASS; OpenAPI 135 paths / 172 operations, 9 onboarding operations / 6 paths; 96 tests pass). |
||||
|
- 2026-09-17T01:50:30Z W3 wave review PASS (conditional within N04): 96 tests, RR boundary 1, randomized E2E 7, OpenAPI 135 paths/9 operations; external blockers are existing full-suite failures and pending MySQL/Docker acceptance. |
||||
|
- 2026-09-17T02:47:50Z DP-6 pass with formal waiver: user accepted existing non-N04 full-suite failures; real MySQL/Docker remains a pre-release gate. |
||||
|
- 2026-09-17T02:48:23Z Spec merge completed manually: onboarding-checklist 6 requirements / 20 scenarios, company-knowledge 6 requirements / 16 scenarios; no conflicts. |
||||
|
- 2026-09-17T08:03:00Z DP-7 confirmed; state transitioned to closing; manual no-ff merge to master authorized. |
||||
@ -0,0 +1,59 @@ |
|||||
|
# W1 T1 Review: V38/V39 Migrations and Standard Seed |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** Approved |
||||
|
- **Overall:** PASS |
||||
|
- **Base:** `a2c66b3` |
||||
|
- **Head:** `7fe96cc` |
||||
|
- **Blocking findings:** none |
||||
|
|
||||
|
## Verification |
||||
|
|
||||
|
- Inspected the complete `a2c66b3..7fe96cc` diff, T1 brief, execution contract, design data model, V1 schema for FK type compatibility, and both new tests. |
||||
|
- Re-ran the exact T1 evidence command from `mydreamjob-server`: |
||||
|
`mvn -Dtest=OnboardingMigrationTest,OnboardingSeedDataIntegrationTest test` |
||||
|
- Result: `Tests run: 2, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
|
||||
|
## Spec Compliance |
||||
|
|
||||
|
- V38 creates all three required tables and matches the approved column set, lengths, nullability, defaults, FK targets, uniqueness rules, and active/sort index in `design.md`: `V38__onboarding_checklist_and_company_knowledge.sql:4-57`. |
||||
|
- FK child/parent types are compatible with the existing `users.id` and `companies.id` BIGINT primary keys (`V1__base_schema.sql:5-6` and the existing company schema). |
||||
|
- V39 contains 12 fixed standard-checklist rows with explicit `item_key`, title, category, `sort_order`, `required`, and `active` values; there is no time-, request-, or random-dependent seed input (`V39__onboarding_standard_checklist_seed.sql:4-31`). |
||||
|
- `OnboardingMigrationTest.java:37-60` verifies the three tables, required columns, four unique indexes, four foreign keys, and the active/sort index. |
||||
|
- `OnboardingSeedDataIntegrationTest.java:51-79` verifies exact seed cardinality, content, category, required/active flags, `scope_type`, `scope_id`, and stable `sort_order ASC, id ASC` ordering. |
||||
|
- The diff adds only the two migrations plus their tests and SDD task-brief artifacts. No V1-V37 migration, existing API, or W2 entity/repository/service/controller was modified. |
||||
|
- Static inspection supports H2 `MODE=MySQL` and MySQL 8.4 syntax compatibility; the focused H2 test context passed. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical (Must Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important (Should Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Minor (Nice to Have) |
||||
|
|
||||
|
- `OnboardingMigrationTest.java:38-60` checks column presence but not the migrated column types, nullability, defaults, or CHECK constraints. The generated SQL matches the design and the key unique/FK/index obligations are covered, so this is only a future drift-detection gap. |
||||
|
- The T1 commit also adds `T2.md`, which is outside the T1 Files In Scope (`task-briefs/T1.md:14-19`). It has no runtime or contract impact, but implementation commits should normally remain limited to controller-created planning artifacts or T1-owned files. |
||||
|
|
||||
|
## Residual Verification Warning |
||||
|
|
||||
|
- Real MySQL 8.4 execution was not performed in this review. The SQL is compatible by inspection and the T1 Done Definition's two H2 evidence tests pass, but the B1 verification gate should retain the contract's real-MySQL check before final acceptance. |
||||
|
- TDD RED is credibly reported with a missing-table failure and the current checkout independently reproduces GREEN. The RED order cannot be reconstructed from the single final commit, because tests and migrations were committed together; this does not affect the implementation verdict. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Task quality:** Approved |
||||
|
|
||||
|
**Reasoning:** T1 implements the approved schema and deterministic standard seed with appropriate focused integration coverage, no scope-breaking implementation changes, and a reproducible green evidence run. |
||||
|
|
||||
|
Controller receipt command when `ssf` is available: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W1 --base a2c66b3 --head 7fe96cc --report changes/n04-onboard/.superpowers/sdd/reviews/W1-T1.md --verdict pass |
||||
|
``` |
||||
@ -0,0 +1,72 @@ |
|||||
|
# W1 T2 Review: Onboarding Entities, Repositories, and Review Aggregation |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** Approved |
||||
|
- **Overall:** PASS |
||||
|
- **Base:** `7fe96cc` |
||||
|
- **Head:** `ef4a9b8` |
||||
|
- **Blocking findings:** none |
||||
|
- **Cannot verify:** real MySQL 8.4 schema validation and JSON/tag retrieval; H2 `MODE=MySQL` passed. |
||||
|
|
||||
|
## Verification |
||||
|
|
||||
|
- Read `task-reviewer-prompt.md`, `writing-good-tests.md`, T2 brief, execution contract, design data model, V38, the complete `7fe96cc..ef4a9b8` diff, and the existing `JobReview`, `JobReviewDimensionScore`, `Job`, and `Company` mappings needed to judge the named risks. |
||||
|
- Scope check: the diff contains only the nine files allowed by the T2 brief, with 732 added lines and no unrelated or modified historical files. |
||||
|
- Re-ran the focused T2 evidence command from `mydreamjob-server` with `JAVA_HOME=D:\java` and Maven `3.9.7`. |
||||
|
- Result: `Tests run: 5, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
- `git diff --check 7fe96cc..ef4a9b8` reported no whitespace errors. |
||||
|
|
||||
|
## Spec Compliance |
||||
|
|
||||
|
- `OnboardingChecklistItem.java:27-94` maps the approved table, all fields, unique constraints, defaults, optimistic version, and audit timestamps against `V38__onboarding_checklist_and_company_knowledge.sql:4-30`. |
||||
|
- `request_hash` is mapped with `CHAR(64)` at `OnboardingChecklistItem.java:80`, matching `V38__onboarding_checklist_and_company_knowledge.sql:17`; `OnboardingRepositoryIntegrationTest.java:107-133` verifies both persistence and the migrated H2 column type/length. |
||||
|
- `StudentOnboardingProgress.java:27-63` and `CompanyKnowledge.java:27-62` match the remaining V38 columns, nullability, lengths, unique keys, versions, and audit fields. |
||||
|
- `OnboardingChecklistItemRepository.java:13-25` filters `active=true`, selects active standard rows plus active rows for the requested company, requires both company discriminators for company rows, and orders by `sortOrder ASC, id ASC`. |
||||
|
- `StudentOnboardingProgressRepository.java:14-18` provides both the student/item batch lookup and the exact student/item lookup required by T2. |
||||
|
- `CompanyKnowledgeRepository.java:9-11` provides the unique company lookup. |
||||
|
- `JobReviewRepository.java:35-67` aggregates only reviews whose status is `PUBLISHED` and whose moderation status is `AUTO_APPROVED` or `APPROVED`; all company aggregation is scoped through `jobs.company_id`. |
||||
|
- `JobReviewDimensionScoreRepository.java:46-63` performs the eight-dimension aggregation in one database query and applies the same company, publication-status, and moderation-status filters. |
||||
|
- The review aggregation queries avoid loading review entities or issuing per-job/per-review queries, satisfying the no-N+1 requirement. |
||||
|
- `OnboardingRepositoryIntegrationTest.java:78-195` covers active/inactive standard and company items, cross-company isolation, stable ordering, request-hash mapping, student/item progress scoping, and company knowledge scoping. |
||||
|
- `OnboardingRepositoryIntegrationTest.java:198-264` covers company scoping, moderation filtering, score distribution, raw tags, and dimension aggregation. Tags are selected as the raw database column and ordered deterministically by review ID, as required. |
||||
|
- No service, controller, DTO, state machine, migration, or unrelated production file was added by T2. |
||||
|
|
||||
|
## Strengths |
||||
|
|
||||
|
- The entity mappings follow the approved V38 schema closely, including the non-obvious `CHAR(64)` request hash. |
||||
|
- The effective-checklist query encodes both company ownership discriminators, preventing a cross-company item from leaking through a mismatched `scope_id` or `company_id`. |
||||
|
- Company review aggregation is implemented as set-based JPQL and consistently applies public-status rules in every query. |
||||
|
- The integration test uses real Spring Data/JPA persistence and independently checks cross-company and moderation boundaries rather than mocking repository behavior. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical (Must Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important (Should Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Minor (Nice to Have) |
||||
|
|
||||
|
- `OnboardingRepositoryIntegrationTest.java:217-219` labels a review as hidden by setting `moderationStatus=PENDING`, but every company-A review still has `status=PUBLISHED`. A production mutation removing the `r.status = 'PUBLISHED'` predicate from all three company queries would therefore still pass the test. Add a review with `status=HIDDEN` and `moderationStatus=APPROVED`, then assert it is excluded from count, rating distribution, tags, and dimensions. The production queries are currently correct, so this is a coverage gap rather than a runtime defect. |
||||
|
- The checklist query trusts `scope_type=STANDARD` without also enforcing the approved `scope_id=0`/`company_id IS NULL` invariant. The design explicitly assigns that consistency check to the application layer and T2 adds no standard-writing path, so this is only defense in depth. |
||||
|
|
||||
|
## Residual Verification Warning |
||||
|
|
||||
|
- The implementer correctly disclosed that real MySQL 8.4 was not exercised. H2 `MODE=MySQL` validates the new mappings and executes the aggregation queries successfully, but MySQL schema validation and JSON/tag returns remain a B1 verification-gate check. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Task quality:** Approved |
||||
|
|
||||
|
**Reasoning:** T2 implements the approved persistence mappings and repository queries with correct scoping and public-review filtering, keeps the diff within scope, and passes focused integration evidence. The only findings are non-blocking coverage/defense-in-depth suggestions. |
||||
|
|
||||
|
Controller receipt command when `ssf` is available: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W1 --base 7fe96cc --head ef4a9b8 --report changes/n04-onboard/.superpowers/sdd/reviews/W1-T2.md --verdict pass |
||||
|
``` |
||||
@ -0,0 +1,150 @@ |
|||||
|
# W1 Wave Review: Onboarding Data Model and Read-Only Query Foundation |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Wave:** W1 |
||||
|
- **Base:** `a2c66b3` |
||||
|
- **Head:** `ef4a9b8` |
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** PASS |
||||
|
- **Overall:** PASS |
||||
|
- **Blocking findings:** None |
||||
|
|
||||
|
## Scope Reviewed |
||||
|
|
||||
|
Read and checked: |
||||
|
|
||||
|
- `changes/n04-onboard/execution-contract.md` |
||||
|
- `changes/n04-onboard/specs/onboarding-checklist/spec.md` |
||||
|
- `changes/n04-onboard/specs/company-knowledge/spec.md` |
||||
|
- `changes/n04-onboard/design.md` |
||||
|
- `changes/n04-onboard/.superpowers/sdd/task-briefs/T1.md` |
||||
|
- `changes/n04-onboard/.superpowers/sdd/task-briefs/T2.md` |
||||
|
- `changes/n04-onboard/.superpowers/sdd/reviews/W1-T1.md` |
||||
|
- `changes/n04-onboard/.superpowers/sdd/reviews/W1-T2.md` |
||||
|
- Complete `a2c66b3..ef4a9b8` implementation diff, including migrations, entities, repositories, review aggregation queries, and integration tests. |
||||
|
|
||||
|
`git diff --check a2c66b3..ef4a9b8` reported no whitespace errors. The wave contains no service, controller, DTO, or state-machine implementation, matching W1 scope. |
||||
|
|
||||
|
## Test Evidence |
||||
|
|
||||
|
Command run from `mydreamjob-server`: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java'; $env:Path='D:\java\bin;'+$env:Path |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=OnboardingMigrationTest,OnboardingSeedDataIntegrationTest,OnboardingRepositoryIntegrationTest' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
Per-class result: |
||||
|
|
||||
|
- `OnboardingMigrationTest`: 1 test, 0 failures/errors/skips. |
||||
|
- `OnboardingRepositoryIntegrationTest`: 5 tests, 0 failures/errors/skips. |
||||
|
- `OnboardingSeedDataIntegrationTest`: 1 test, 0 failures/errors/skips. |
||||
|
|
||||
|
## Cross-Task Integration Review |
||||
|
|
||||
|
### Migrations vs Entity Mappings |
||||
|
|
||||
|
PASS. |
||||
|
|
||||
|
- V38 creates all three approved tables with the contract column set, nullability, lengths, defaults, foreign keys, uniqueness rules, and active/sort index. |
||||
|
- Entity mappings match V38 for all three tables: `OnboardingChecklistItem`, `StudentOnboardingProgress`, and `CompanyKnowledge`. |
||||
|
- `request_hash` is mapped explicitly as `CHAR(64)`, matching V38 and the design requirement. |
||||
|
- Unique constraint annotations on the entities match the database constraint names and column sets: |
||||
|
- `uk_onboarding_checklist_scope_item` |
||||
|
- `uk_onboarding_checklist_company_idempotency` |
||||
|
- `uk_student_onboarding_progress_student_item` |
||||
|
- `uk_company_knowledge_company` |
||||
|
- The new entities are validated under H2 `MODE=MySQL` with `ddl-auto=validate`; the test scopes validation to the three new onboarding tables because an unrelated legacy H2 type mismatch exists in `auth_tokens`. |
||||
|
- No V1-V37 migration or existing table definition was modified. |
||||
|
|
||||
|
### Unique Constraints and Version Fields |
||||
|
|
||||
|
PASS. |
||||
|
|
||||
|
- Checklist item uniqueness matches the design for standard/company scope and per-company idempotency. |
||||
|
- Student progress is unique on `(student_user_id, item_id)`. |
||||
|
- Company knowledge is unique on `company_id`. |
||||
|
- All three entities use `@Version Long` with builder default `0L`, matching the database `version BIGINT NOT NULL DEFAULT 0`. |
||||
|
- No cross-task mismatch was found between database defaults and entity initial values. |
||||
|
|
||||
|
### Effective Checklist Query |
||||
|
|
||||
|
PASS for W1. |
||||
|
|
||||
|
- `findEffectiveChecklistItems(companyId)` returns only active rows. |
||||
|
- It includes active standard rows and active company rows only when both `scopeId` and `companyId` equal the requested company. |
||||
|
- Ordering is deterministic: `sortOrder ASC, id ASC`. |
||||
|
- Integration coverage verifies inactive standard/company items are excluded, other-company items are excluded, and equal-sort-order rows are stable by ID. |
||||
|
- Non-blocking follow-up: the query trusts `scope_type=STANDARD` without also requiring `scope_id=0` and `company_id IS NULL`. The design assigns that consistency invariant to the application layer, and no W1 write path can create malformed standard rows. W2 should preserve that invariant when adding services. |
||||
|
|
||||
|
### Progress Lookup |
||||
|
|
||||
|
PASS for W1. |
||||
|
|
||||
|
- Batch lookup is scoped by `studentUserId` and a collection of item IDs. |
||||
|
- Exact lookup is scoped by `studentUserId` and `itemId`. |
||||
|
- The database/entity unique constraint guarantees at most one progress row per student/item pair. |
||||
|
- Integration coverage verifies multi-student isolation and correct `completedAt` retrieval. |
||||
|
- Non-blocking follow-up for W2: first-write races must be translated through the unique constraint into the approved idempotent/concurrent behavior; W2 also needs to define behavior for an empty item collection if that path is possible. |
||||
|
|
||||
|
### Public-Only Company Review Aggregation |
||||
|
|
||||
|
PASS. |
||||
|
|
||||
|
- Rating count/average, rating distribution, and tag queries all restrict to `status = 'PUBLISHED'` and moderation status `IN ('AUTO_APPROVED', 'APPROVED')`. |
||||
|
- Dimension aggregation applies the same publication and moderation predicates. |
||||
|
- Every company-level query scopes reviews through `jobs.company_id`; other-company data is excluded. |
||||
|
- Aggregation is set-based and does not introduce an obvious N+1 path. |
||||
|
- Integration coverage verifies company isolation, moderation filtering, rating distribution, tags, and eight-dimension aggregation. |
||||
|
- Non-blocking coverage follow-up: add a review with `status=HIDDEN` and `moderationStatus=APPROVED` to prove the status predicate independently of the moderation predicate. The production queries are currently correct. |
||||
|
|
||||
|
### Scope Discipline |
||||
|
|
||||
|
PASS. |
||||
|
|
||||
|
- Production changes are limited to the three migrations, six onboarding persistence files, and additive methods in the two existing review repositories. |
||||
|
- No existing API behavior, historical migration, or unrelated production file was changed. |
||||
|
- T1 review noted that its commit also added the T2 task brief; this is planning-artifact housekeeping and has no runtime or contract impact. |
||||
|
- No W2/W3 service, controller, DTO, security, or OpenAPI implementation was introduced. |
||||
|
|
||||
|
### Readiness for W2 |
||||
|
|
||||
|
PASS with documented follow-ups. |
||||
|
|
||||
|
W2 can build on the W1 interfaces for effective checklist reads, student/item progress lookup, company knowledge lookup, and public company review aggregation. Before W2 closes, it must: |
||||
|
|
||||
|
- Validate and enforce standard/company scope consistency in service code. |
||||
|
- Handle progress insertion races and version conflicts according to 9705 semantics. |
||||
|
- Preserve the required PUBLISHED + approved moderation filter in all knowledge-summary paths. |
||||
|
- Parse raw JSON tags robustly across H2 and MySQL, since H2 returns the raw JSON column in a representation that may include outer quoting. |
||||
|
- Preserve deterministic tie-breaking for top tags and ordering of response data. |
||||
|
- Keep the real MySQL 8.4 migration/schema check as the B1 verification-gate obligation; H2 `MODE=MySQL` passed, but real MySQL was not run in this wave. |
||||
|
|
||||
|
## Findings |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
- Add the `status=HIDDEN, moderationStatus=APPROVED` aggregation test case noted above. |
||||
|
- Add defense-in-depth scope-consistency enforcement for standard rows in the effective-checklist service path. |
||||
|
- Preserve the existing validation-filter workaround as test-only infrastructure; it should not mask new onboarding mapping mismatches. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
W1 is internally consistent and ready to hand off to W2. The migrations, entity mappings, repository queries, and focused integration evidence agree with the approved contract and both specs. No blocking finding prevents W2 from starting. |
||||
|
|
||||
@ -0,0 +1,75 @@ |
|||||
|
# W2 T3 Review: Student Checklist and Completion APIs |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** Approved |
||||
|
- **Overall:** PASS |
||||
|
- **Base:** `3e7c6e7` |
||||
|
- **Head:** `037ddc7` |
||||
|
- **Blocking findings:** none |
||||
|
- **Cannot verify from this task alone:** actual MySQL persistence behavior and a concurrent first-create race; these remain B3 integration-gate checks. |
||||
|
|
||||
|
## Verification |
||||
|
|
||||
|
- Read `task-reviewer-prompt.md`, `writing-good-tests.md`, the T3 brief, execution contract, checklist specification, design response/error contracts, the complete `3e7c6e7..037ddc7` diff, and focused source/test files where the diff output was truncated. |
||||
|
- Scope check: the diff contains exactly ten files: the eight planned T3 files plus additive changes to `ErrorCode.java` and `GlobalExceptionHandler.java`. |
||||
|
- Re-ran the T3 evidence command from `mydreamjob-server` with `JAVA_HOME=D:\java` and Maven 3.9.7. |
||||
|
- Result: `Tests run: 15, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
- `git diff --check 3e7c6e7..037ddc7` reported no whitespace errors. |
||||
|
- The final commit cannot reconstruct RED-before-GREEN ordering, but the independently reproduced current HEAD is GREEN. |
||||
|
|
||||
|
## Spec Compliance |
||||
|
|
||||
|
- Effective checklist fallback is correct: `OnboardingChecklistService.java:45-48` delegates to `OnboardingChecklistItemRepository.java:13-25`, whose query returns active standard items plus active items for the requested company only. `OnboardingChecklistServiceTest.java:49-74` verifies the standard-only fallback and `customized=false`. |
||||
|
- Merging and stable ordering are correct: `OnboardingChecklistService.java:114-126` sorts by `sortOrder ASC, id ASC`; `:154-169` builds the merged response and sets `customized` from the presence of a `COMPANY` item. `OnboardingChecklistServiceTest.java:77-103` independently verifies order `2,3,1`, source, progress, and `customized=true`. |
||||
|
- Company existence is enforced before checklist/item/progress work in both GET and PUT paths (`OnboardingChecklistService.java:42-43`, `:62-63`). `OnboardingChecklistServiceTest.java:121-130` verifies `COMPANY_NOT_FOUND` and no later repository interaction. |
||||
|
- STUDENT-only access is enforced at service level with `FORBIDDEN` for non-students (`OnboardingChecklistService.java:104-111`); `OnboardingChecklistServiceTest.java:133-142` verifies the behavior before company lookup. |
||||
|
- Completion PUT uses absolute state semantics and avoids writes for an identical state (`OnboardingChecklistService.java:77-89`). `OnboardingChecklistServiceTest.java:175-191` verifies a stale version is ignored for an identical state, returns current version/completedAt, and performs no save. |
||||
|
- First-create behavior is correct for the service contract: a new progress entity defaults to version `0` (`StudentOnboardingProgress.java:52-55`), is saved once, and the response returns version `0` when no persisted version exists (`OnboardingChecklistService.java:90-99`, `:185-186`). `OnboardingChecklistServiceTest.java:145-172` verifies the inserted entity, `completedAt`, count, total, and rate. |
||||
|
- Stale-version conflict behavior is correct: a different desired state with a non-current version raises `ONBOARDING_PROGRESS_CONCURRENT_UPDATE` before mutation (`OnboardingChecklistService.java:82-85`), defined as `9705` in `ErrorCode.java:177`, and mapped to HTTP 409 in `GlobalExceptionHandler.java:167-169`. `OnboardingChecklistServiceTest.java:193-210` verifies the error code and no save. |
||||
|
- `completedAt` lifecycle is correct: first completion writes a timestamp, cancellation clears it, and identical-state requests preserve it (`OnboardingChecklistService.java:86-95`, `:183-184`). Covered by `OnboardingChecklistServiceTest.java:145-172`, `:175-191`, and `:213-238`. |
||||
|
- Completion-rate math is correct and deterministic: the service uses the effective item IDs, counts only completed rows, returns `0.0` for an empty list, and rounds one decimal place (`OnboardingChecklistService.java:215-233`). Tests cover `0/1`, `1/1`, empty, orphan-row exclusion, and `1/3 = 33.3` (`OnboardingChecklistServiceTest.java:49-118`, `:77-103`). |
||||
|
- Item scoping is correct: PUT resolves the item from the effective checklist query, so an item from another company, an inactive item, or a missing item produces `9701` without touching progress (`OnboardingChecklistService.java:65-71`); `OnboardingChecklistServiceTest.java:240-253` verifies no progress interaction. |
||||
|
- DTOs are whitelist-only and contain no owner, token, IP, moderation, or internal audit fields (`ChecklistItemView.java:14-27`, `EffectiveChecklistResponse.java:14-22`, `ProgressUpdateRequest.java:8-14`, `ProgressUpdateResponse.java:14-22`). Controller tests assert the externally visible response contract (`OnboardingChecklistControllerTest.java:44-103`). |
||||
|
- The two task errors are mapped correctly: `9701` maps to 404 and `9705` maps to 409 (`ErrorCode.java:175-177`, `GlobalExceptionHandler.java:65-80`, `:167-169`). The service tests independently assert the business error codes. |
||||
|
|
||||
|
## Test Falsifiability |
||||
|
|
||||
|
- The service tests exercise observable service behavior rather than implementation-only helpers: fallback, merge order, progress aggregation, idempotency, conflict, timestamp lifecycle, and scoping each have a plausible production mutation that would fail them. |
||||
|
- The controller tests exercise real MockMvc security/exception handling with a mocked service; they verify authentication, role error propagation, company-not-found status, request identity, and serialized DTO fields. |
||||
|
- `OnboardingChecklistServiceTest.java:77-103` specifically catches removal or incorrect rounding of the one-decimal completion rate through `1/3 = 33.3`. |
||||
|
- The main test gap is status mapping for `9705`: the service test proves the error code and `GlobalExceptionHandler.java:168-169` proves the 409 branch statically, but no controller/integration test would fail if that handler branch were removed. This is non-blocking because the mapping is present and B3 owns the HTTP integration matrix. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical (Must Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important (Should Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Minor (Nice to Have) |
||||
|
|
||||
|
- `OnboardingChecklistControllerTest.java:105-135` covers 401, 403, and 404 but has no PUT case returning 409 for `ONBOARDING_PROGRESS_CONCURRENT_UPDATE`. Add a focused MockMvc assertion for status 409 and code 9705, or retain it as the B3 end-to-end obligation. |
||||
|
- `GlobalExceptionHandler.java:65-80` and `ErrorCode.java:175-177` are outside the explicit eight-file T3 scope (`task-briefs/T3.md:14-23`). Both changes are additive and necessary for the required 404/409 contract, and `ErrorCode.java` is explicitly allowed by the execution contract; the task brief and T6 ownership should be reconciled so T6 does not duplicate or unintentionally renumber them. |
||||
|
- The focused test run emits existing Spring/Mockito/H2 configuration warnings from the full `@SpringBootTest` controller context (`OnboardingChecklistControllerTest.java:34-42`). They do not affect the result, but a cleaner test context would reduce output noise. |
||||
|
|
||||
|
## Residual Verification Warning |
||||
|
|
||||
|
- The tests use mocks for the first insert, so the actual JPA insert version and a simultaneous first-create race are not established here. The current code can rely on the unique constraint for sequential idempotency, but concurrent first inserts should be exercised at the B3 gate. |
||||
|
- Real MySQL 8.4 execution was not performed; H2 `MODE=MySQL` passed and the remaining MySQL proof belongs to the verification gate. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Task quality:** Approved |
||||
|
|
||||
|
**Reasoning:** T3 implements the approved student checklist and completion behavior with correct fallback, ordering, scoping, idempotency, timestamp lifecycle, rate calculation, DTO minimization, and error-code behavior. The only findings are non-blocking test/plan reconciliation items. |
||||
|
|
||||
|
Controller receipt command when `ssf` is available: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W2 --base 3e7c6e7 --head 037ddc7 --report changes/n04-onboard/.superpowers/sdd/reviews/W2-T3.md --verdict pass |
||||
|
``` |
||||
@ -0,0 +1,94 @@ |
|||||
|
# W2 T4 Review: Company Custom Checklist CRUD and Idempotency |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** Approved |
||||
|
- **Overall:** PASS |
||||
|
- **Base:** `037ddc7` |
||||
|
- **Head:** `7370e51` |
||||
|
- **Blocking findings:** none |
||||
|
- **Deferred blocker:** concurrent create / 50-item limit races are not resolved by this task and must be treated as mandatory T6 concurrency-gate obligations. |
||||
|
|
||||
|
## Verification |
||||
|
|
||||
|
- Read `task-reviewer-prompt.md`, `writing-good-tests.md`, `task-briefs/T4.md`, the execution contract, `specs/onboarding-checklist/spec.md`, the design, and the complete `037ddc7..7370e51` diff. Focused reads were used only where the diff output was truncated mid-function. |
||||
|
- Scope check: the commit changes 9 files: the 7 files named by the T4 brief plus additive changes to `ErrorCode.java` and `GlobalExceptionHandler.java`. |
||||
|
- Re-ran the T4 evidence command from `mydreamjob-server` with `JAVA_HOME=D:\java` and Maven 3.9.7. |
||||
|
- Result: `Tests run: 22, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
- `git diff --check 037ddc7..7370e51` reported no whitespace errors. |
||||
|
- RED-before-GREEN ordering cannot be reconstructed from the final commit; the independently reproduced current HEAD is GREEN. |
||||
|
|
||||
|
## Spec Compliance |
||||
|
|
||||
|
- COMPANY ownership is enforced centrally: `requireCompanyOwner` checks the COMPANY role and resolves the company by `ownerUserId`, missing roles return FORBIDDEN, and missing company profiles return COMPANY_NOT_FOUND (`OnboardingChecklistService.java:229-239`). The role/profile tests are at `OnboardingCompanyChecklistServiceTest.java:269-297`. |
||||
|
- Company listing is isolated to the current company and includes inactive rows: `findAll()` results are filtered by `scopeType=COMPANY`, `scopeId`, and `companyId`, then sorted (`OnboardingChecklistService.java:121-131`, `:241-257`). The test verifies own active/inactive items and excludes another company and a STANDARD item (`OnboardingCompanyChecklistServiceTest.java:47-68`). |
||||
|
- Create validation covers title, description, category, sortOrder, required default, and idempotency-key length in both the request DTO and service (`CompanyChecklistItemCreateRequest.java:9-33`, `OnboardingChecklistService.java:266-289`). Invalid requests return `BAD_REQUEST` before repository access (`OnboardingCompanyChecklistServiceTest.java:71-88`). |
||||
|
- Create normalization and hashing are deterministic: title/description/idempotency key are trimmed, blank optional strings become null, required defaults to false, sortOrder defaults to 1000, and the canonical request is SHA-256 hashed (`OnboardingChecklistService.java:140-148`, `:315-349`). The persistence test verifies the normalized entity and hash shape (`OnboardingCompanyChecklistServiceTest.java:91-122`). |
||||
|
- Sequential idempotency replay and conflict behavior are correct: an existing `(company, idempotencyKey)` with the same hash returns the original row without a second save; a different hash raises 9704 (`OnboardingChecklistService.java:150-163`; tests at `OnboardingCompanyChecklistServiceTest.java:125-166`). |
||||
|
- The 50-active-item boundary is enforced in the sequential path: active rows are counted, and 50 existing active rows produce 9703 before save (`OnboardingChecklistService.java:165-170`; test at `OnboardingCompanyChecklistServiceTest.java:169-185`). |
||||
|
- Update applies every mutable field and checks the supplied version before mutation (`OnboardingChecklistService.java:189-209`). Stale versions return 9702 and do not save (`OnboardingCompanyChecklistServiceTest.java:188-228`). |
||||
|
- Delete is a soft delete and repeated deletion is idempotent: the active flag is set false with the current version, then a second delete returns the already-inactive row without another save (`OnboardingChecklistService.java:211-227`; test at `OnboardingCompanyChecklistServiceTest.java:231-251`). |
||||
|
- Cross-company mutation is blocked by the same ownership predicate used by update and delete; an item belonging to another company returns 9701 and remains unchanged (`OnboardingChecklistService.java:252-264`; delete isolation test at `OnboardingCompanyChecklistServiceTest.java:254-266`). |
||||
|
- The response DTO is whitelist-only and exposes item identity, source, key, display fields, sort/active/version; it does not expose company IDs, owner IDs, idempotency keys, request hashes, timestamps, or internal audit fields (`CompanyChecklistItemView.java:14-23`). |
||||
|
- Error mappings are correct: 9702, 9703, and 9704 are defined (`ErrorCode.java:175-179`) and map to HTTP 409 (`GlobalExceptionHandler.java:167-172`); 9701 remains mapped to 404. Controller tests cover 409 for 9702/9704 and 404 for 9701 (`OnboardingCompanyChecklistControllerTest.java:120-132`, `:167-189`, `:211-233`). |
||||
|
- Controller wiring matches the fixed endpoints and role-neutral transport contract: all four company CRUD methods resolve the authenticated user and delegate to the service (`OnboardingChecklistController.java:65-108`). Authentication is covered at `OnboardingCompanyChecklistControllerTest.java:235-250`. |
||||
|
|
||||
|
## Test Falsifiability |
||||
|
|
||||
|
- The service tests assert observable results and side effects: persisted scope/company/defaults, replay returning the original ID with one save, conflict preventing a save, stale version preventing mutation, and repeat deletion not saving twice. Each has a plausible production mutation that would fail it. |
||||
|
- Controller tests exercise real MockMvc security, validation, serialization, and exception mapping with a mocked service. They would catch wrong paths, missing role passthrough, weak request validation, and incorrect 409/404 mappings. |
||||
|
- The 50-item test catches blocking at exactly 50, but does not independently prove that 49 active items are allowed or that inactive items do not count toward the limit. The implementation is correct by inspection, but the lower boundary/filtering semantics are not fully falsifiable from the current test. |
||||
|
- Cross-company isolation is directly tested for delete only. Update and list use the same ownership predicate, and the controller test mocks the cross-company update result, so the service-level update isolation path is not independently exercised. |
||||
|
- The HTTP mapping for 9703 is statically present but has no focused MockMvc assertion; only the service error code is tested. This is non-blocking because T6 owns the full HTTP/concurrency integration gate. |
||||
|
|
||||
|
## Concurrent Create / Limit Race Disposition |
||||
|
|
||||
|
The reported race is real, but it is **not a blocking finding for T4**. |
||||
|
|
||||
|
- The current create path reads `findAll()` and checks idempotency/active count before inserting (`OnboardingChecklistService.java:150-186`). There is no row lock or atomic counter. |
||||
|
- V38 does have a unique key on `(company_id, idempotency_key)` (`V38__onboarding_checklist_and_company_knowledge.sql`/equivalent migration line 27; entity declaration `OnboardingChecklistItem.java:29-35`). Simultaneous identical creates can therefore make one request fail with `DataIntegrityViolationException`; `saveCompanyItem` only translates optimistic-lock failures (`OnboardingChecklistService.java:366-373`), so the loser can surface as a 500 instead of a safe replay. |
||||
|
- The 50-item limit is also non-atomic: two concurrent creates at 49 active items can both observe 49 and insert, producing 51 active items. |
||||
|
- T4's brief and Done Definition require sequential validation, replay/conflict, and the 50-item boundary, but do not assign concurrent execution to T4. The execution contract maps concurrency to B3, and the T6 brief explicitly requires concurrent/idempotent conflict behavior, error mapping, no partial writes, and the concurrency integration test (`task-briefs/T6.md:12`, `:18`, `:21`, `:30-33`, `:51`). |
||||
|
|
||||
|
Mandatory T6 test obligations: |
||||
|
|
||||
|
1. Fire two concurrent creates with the same company, key, and normalized body: assert exactly one row, both callers receive the same successful item (or the loser receives the same successful replay), and neither receives an internal/500 response. |
||||
|
2. Fire two concurrent creates with the same key and differing bodies: assert exactly one row, one successful create, and one 409 `9704`, not a unique-constraint 500. |
||||
|
3. Start with 49 active items and concurrently create two distinct items: assert the final active count is 50, one request succeeds, one returns 409 `9703`, and no partial/extra row remains. |
||||
|
4. Start at 50 active items and verify new creates return 409 `9703`; verify replaying an existing idempotency key still returns the original item without counting as a new item. |
||||
|
5. Repeat the key test across two companies to prove `(company_id, idempotency_key)` isolation and no cross-company replay. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical (Must Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important (Should Fix) |
||||
|
|
||||
|
None for T4. The concurrent create/limit race is deferred by the approved execution plan to T6, but it must not be closed without the obligations above. |
||||
|
|
||||
|
### Minor (Nice to Have) |
||||
|
|
||||
|
- Strengthen `OnboardingCompanyChecklistServiceTest.java:169-185` with a 49-active success case and an inactive-item-does-not-count case; the current test proves the upper rejection only. |
||||
|
- Add a service-level cross-company update test alongside the existing delete isolation test, or explicitly retain this as a T6 integration obligation. |
||||
|
- Add a focused MockMvc assertion for 9703 -> HTTP 409, since only the service error code is currently exercised for the limit path. |
||||
|
- `ErrorCode.java` and `GlobalExceptionHandler.java` are outside the seven files named by `task-briefs/T4.md:14-23`. The additions are limited to the T4 error codes and are necessary for the required 409 contract; reconcile ownership in the task ledger so T6 does not duplicate or renumber them. |
||||
|
|
||||
|
## Residual Verification Warning |
||||
|
|
||||
|
- Real MySQL 8.4 execution and true concurrent JPA behavior were not exercised by this T4 unit/MockMvc suite. H2-backed MockMvc tests passed; the remaining database/concurrency proof belongs to the B3/T6 verification gate. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Task quality:** Approved |
||||
|
|
||||
|
**Reasoning:** T4 implements the approved sequential company CRUD, ownership isolation, validation, deterministic idempotency replay/conflict, optimistic update, soft-delete idempotency, DTO minimization, and error-code mappings. The only blocking-risk item is the explicitly deferred concurrent create/limit race, which must be proven and fixed at T6. |
||||
|
|
||||
|
Controller receipt command when `ssf` is available: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W2 --base 037ddc7 --head 7370e51 --report changes/n04-onboard/.superpowers/sdd/reviews/W2-T4.md --verdict pass |
||||
|
``` |
||||
|
|
||||
@ -0,0 +1,75 @@ |
|||||
|
# W2-T5 Review: Company Knowledge Configuration and Public Review Summary |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- Spec compliance: ✅ Spec compliant |
||||
|
- Task quality: **Approved** |
||||
|
- Blocking findings: **None** |
||||
|
|
||||
|
## Scope Reviewed |
||||
|
|
||||
|
- Task brief: `changes/n04-onboard/.superpowers/sdd/task-briefs/T5.md` |
||||
|
- Execution contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Spec: `changes/n04-onboard/specs/company-knowledge/spec.md` |
||||
|
- Diff: `7370e51..a5730a7` |
||||
|
- Implementer report: unverified implementer claims were checked against the diff and focused tests. |
||||
|
|
||||
|
## Strengths |
||||
|
|
||||
|
- The two endpoint groups use the required paths and contracts: COMPANY configuration is exposed at `CompanyKnowledgeController.java:32-49`, and the STUDENT company overview is exposed at `CompanyKnowledgeController.java:52-60`. |
||||
|
- COMPANY ownership and required role checks are enforced in `CompanyKnowledgeService.java:357-379`; STUDENT overview access is independently checked in the same service. |
||||
|
- Brand-new configuration returns `configured=false`, version `0`, and null content at `CompanyKnowledgeService.java:62-70`. Configured reads return the expected content and version at `CompanyKnowledgeService.java:323-331`. |
||||
|
- Upsert validation enforces the required per-field limits, at least one non-empty field, trimming, and HTML-tag rejection at `CompanyKnowledgeService.java:132-164`. Existing records require a current version and stale versions return 9706 at `CompanyKnowledgeService.java:92-102`. |
||||
|
- The company overview uses whitelist DTOs only: `CompanyKnowledgeOverviewResponse.java:16-52` excludes `ownerUserId` and other internal company fields, while `ReviewSummaryView.java:17-35` contains no review hashes, moderation reasons, IP, or User-Agent data. |
||||
|
- Public-review filtering is correctly applied at the query layer: `JobReviewRepository.java:35-67` and `JobReviewDimensionScoreRepository.java:46-63` constrain the company scope, `status='PUBLISHED'`, and moderation status `AUTO_APPROVED`/`APPROVED`. |
||||
|
- Threshold behavior is implemented at `CompanyKnowledgeService.java:166-191`: `EMPTY`, `INSUFFICIENT`, and `SUFFICIENT` are distinguished at 0, 1-2, and 3+ reviews, with aggregate queries skipped below 3. |
||||
|
- Tag parsing, deterministic count-descending/lexicographic sorting, and the 10-tag cap are implemented at `CompanyKnowledgeService.java:237-276`. The eight-dimension output is complete and deterministic at `CompanyKnowledgeService.java:287-320`, including zero-sample `INSUFFICIENT` dimensions. |
||||
|
- Error 9706 is defined and mapped to HTTP 409 at `ErrorCode.java:181` and `GlobalExceptionHandler.java:168-172`. |
||||
|
- The focused behavior tests assert observable service and HTTP contracts rather than only implementation shape. The public-filter repository integration test independently excludes non-approved and other-company reviews at `OnboardingRepositoryIntegrationTest.java:197-264`. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical (Must Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important (Should Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Minor (Nice to Have) |
||||
|
|
||||
|
- `CompanyKnowledgeServiceTest.java:216-237` covers one public review and `:240-287` covers three, but there is no exact two-review case. A threshold mutation from `< 3` to `< 2` would still pass these tests, leaving the 2/3 boundary insufficiently pinned. Add a two-review test asserting `INSUFFICIENT` and no aggregate output. |
||||
|
- The `INSUFFICIENT` branch test at `CompanyKnowledgeServiceTest.java:234-236` verifies the state, count, and that the distribution query is not called, but it does not assert that `averageRating`, `ratingDistribution`, `topTags`, and `dimensionSummary` are absent from the response. Current production behavior is correct at `CompanyKnowledgeService.java:177-181`, but the test would not catch a future mean-leak regression. |
||||
|
- Tag edge coverage is incomplete at `CompanyKnowledgeServiceTest.java:251-254`: the fixture has only three distinct tags and normal JSON representation. Removing the `.limit(MAX_TOP_TAGS)` guard or breaking the recursive H2 outer-quoted JSON handling would not fail the current suite. Add an 11+ tag case and a quoted JSON fixture if the recursive compatibility behavior is intended to remain supported. |
||||
|
- The public-filter integration test covers `PUBLISHED + PENDING` and another company, but not `HIDDEN + APPROVED`. The query itself explicitly filters status and is correct, so this is a coverage gap rather than a current behavior defect. |
||||
|
- `ErrorCode.java:181` and `GlobalExceptionHandler.java:168-172` are outside the T5 brief's explicit file list. The additive changes are necessary for the required 9706/409 contract, but file ownership should be reconciled during T8/documentation review. |
||||
|
|
||||
|
## Deferred Concurrency Risk |
||||
|
|
||||
|
Two concurrent first-upserts can both observe no existing row; `CompanyKnowledgeService.java:79-89` then attempts two inserts and only `ObjectOptimisticLockingFailureException` is translated at `:123-129`. A unique-key `DataIntegrityViolationException` can therefore escape as a different error. This is not a T5 blocking finding because B3/T6 owns the concurrency gate, but the B3 integration suite should exercise simultaneous first-create and define the expected conflict response. |
||||
|
|
||||
|
## Evidence |
||||
|
|
||||
|
Command run: |
||||
|
|
||||
|
```powershell |
||||
|
$env:JAVA_HOME='D:\java'; $env:Path='D:\java\bin;'+$env:Path |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=CompanyKnowledgeServiceTest,CompanyKnowledgeControllerTest,OnboardingRepositoryIntegrationTest' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 27, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
`git diff --check 7370e51..a5730a7` passed. The test output contains existing Spring/Mockito/H2 configuration warnings (Mockito dynamic agent, explicit H2 dialect, open-in-view, generated security password); they did not affect the result and are not introduced by this task. |
||||
|
|
||||
|
## Receipt Command |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W2-T5 --base 7370e51 --head a5730a7 --report "D:/1/zhi/zhilai/changes/n04-onboard/.superpowers/sdd/reviews/W2-T5.md" --verdict pass |
||||
|
``` |
||||
|
|
||||
@ -0,0 +1,136 @@ |
|||||
|
# W2 Wave Review: Onboarding Checklist, Company CRUD, and Company Knowledge |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Wave:** W2 |
||||
|
- **Base:** `3e7c6e7` |
||||
|
- **Head:** `a5730a7` |
||||
|
- **Verdict:** **PASS** |
||||
|
- **Blocking findings:** None |
||||
|
- **Deferred T6 obligations:** Mandatory, as listed below |
||||
|
|
||||
|
## Scope Reviewed |
||||
|
|
||||
|
Read and cross-checked: |
||||
|
|
||||
|
- `changes/n04-onboard/execution-contract.md` |
||||
|
- `changes/n04-onboard/specs/onboarding-checklist/spec.md` |
||||
|
- `changes/n04-onboard/specs/company-knowledge/spec.md` |
||||
|
- `changes/n04-onboard/design.md` |
||||
|
- `.superpowers/sdd/task-briefs/T3.md` |
||||
|
- `.superpowers/sdd/task-briefs/T4.md` |
||||
|
- `.superpowers/sdd/task-briefs/T5.md` |
||||
|
- `.superpowers/sdd/reviews/W2-T3.md` |
||||
|
- `.superpowers/sdd/reviews/W2-T4.md` |
||||
|
- `.superpowers/sdd/reviews/W2-T5.md` |
||||
|
|
||||
|
Inspected the combined diff `3e7c6e7..a5730a7`, including all production additions for student checklist access, company checklist CRUD, company knowledge configuration/overview, shared error changes, and the associated service/controller tests. |
||||
|
|
||||
|
## Evidence |
||||
|
|
||||
|
Command: |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
$env:JAVA_HOME='D:\java' |
||||
|
$env:Path='D:\java\bin;'+$env:Path |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=Onboarding*Test,CompanyKnowledge*Test' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 66, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
Per-class results: |
||||
|
|
||||
|
- `CompanyKnowledgeControllerTest`: 8 passed |
||||
|
- `CompanyKnowledgeServiceTest`: 14 passed |
||||
|
- `OnboardingChecklistControllerTest`: 5 passed |
||||
|
- `OnboardingChecklistServiceTest`: 10 passed |
||||
|
- `OnboardingCompanyChecklistControllerTest`: 10 passed |
||||
|
- `OnboardingCompanyChecklistServiceTest`: 12 passed |
||||
|
- `OnboardingMigrationTest`: 1 passed |
||||
|
- `OnboardingRepositoryIntegrationTest`: 5 passed |
||||
|
- `OnboardingSeedDataIntegrationTest`: 1 passed |
||||
|
|
||||
|
`git diff --check 3e7c6e7..a5730a7` passed with no whitespace errors. |
||||
|
|
||||
|
## Cross-Task Integration Findings |
||||
|
|
||||
|
### Student checklist and company CRUD |
||||
|
|
||||
|
- Company-created items use the same `onboarding_checklist_items` identity space and predicates consumed by the effective checklist query. |
||||
|
- `OnboardingChecklistItemRepository.findEffectiveChecklistItems` returns active STANDARD items plus active COMPANY items scoped to the requested company, with stable `sortOrder ASC, id ASC` ordering. |
||||
|
- Company soft deletion sets `active=false`, which removes the item from the student effective checklist while preserving the row and any student progress history. |
||||
|
- Student progress is keyed by the shared `itemId`, so a company item created through T4 is addressable through T3 without a translation layer. |
||||
|
- Cross-company ownership checks use the same `scopeType`, `scopeId`, and `companyId` predicate for company listing/update/delete paths. |
||||
|
|
||||
|
### Company knowledge and checklist interactions |
||||
|
|
||||
|
- Checklist and knowledge controllers share only the `/onboarding` namespace and API response conventions; their paths, services, tables, and ownership checks do not collide. |
||||
|
- Company knowledge reads are scoped by `company_id`; student overviews fall back to `BASIC_ONLY` without creating knowledge rows. |
||||
|
- Review aggregation is isolated to published, approved reviews through company jobs and does not mutate checklist or review data. |
||||
|
|
||||
|
### Error-code consistency |
||||
|
|
||||
|
The new codes are defined once and consistently mapped: |
||||
|
|
||||
|
- `9701` `ONBOARDING_CHECKLIST_ITEM_NOT_FOUND` -> HTTP 404 |
||||
|
- `9702` `ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE` -> HTTP 409 |
||||
|
- `9703` `ONBOARDING_ITEM_LIMIT_EXCEEDED` -> HTTP 409 |
||||
|
- `9704` `ONBOARDING_IDEMPOTENCY_CONFLICT` -> HTTP 409 |
||||
|
- `9705` `ONBOARDING_PROGRESS_CONCURRENT_UPDATE` -> HTTP 409 |
||||
|
- `9706` `ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE` -> HTTP 409 |
||||
|
|
||||
|
The shared exception-handler additions preserve the existing mappings for prior error codes. |
||||
|
|
||||
|
### Sensitive-field check |
||||
|
|
||||
|
The W2 DTOs are whitelist-shaped. Internal fields such as `ownerUserId`, `studentUserId`, `requestHash`, raw idempotency keys, review hashes, moderation reasons, tokens, IP addresses, and User-Agent values are not exposed in the onboarding response DTOs. Internal repository/entity field usage is restricted to persistence and ownership logic. |
||||
|
|
||||
|
### Existing API semantics |
||||
|
|
||||
|
The combined diff changes only shared error-code handling outside the new onboarding package. No existing company, job, offer, review, or dimension-score controller path, request contract, response contract, or authorization behavior was changed. The exception-handler edits are additive mappings for the new 97xx codes. |
||||
|
|
||||
|
## Non-Blocking Findings |
||||
|
|
||||
|
1. `GlobalExceptionHandler.java` was modified even though the W2 task briefs list only the onboarding files and `ErrorCode.java` explicitly. The changes are additive, necessary to realize the approved 404/409 contract, and do not alter existing error mappings. Reconcile ownership and scope wording during T6/T8 so the file is not treated as an unaccounted change. |
||||
|
2. The W2 suite uses service tests and MockMvc with mocked services. It does not yet prove a database-backed end-to-end flow from COMPANY create, through STUDENT checklist read, to STUDENT completion update. T6 integration tests should close this cross-layer gap. |
||||
|
3. Existing review coverage still lacks the exact two-review boundary, a full `HIDDEN + APPROVED` exclusion case, a 10-tag cap/quoted-JSON case, and direct MockMvc assertions for some conflict mappings. These are coverage gaps, not current W2 implementation defects. |
||||
|
4. Real MySQL 8.4 execution remains unverified; the W2 suite uses H2 `MODE=MySQL`. B3/T6 verification must run the approved MySQL path before the final gate. |
||||
|
5. `SecurityConfig` currently relies on `anyRequest().authenticated()` for `/onboarding/**`; the explicit path matcher and complete anonymous/STUDENT/COMPANY/ADMIN matrix remain T6 work. |
||||
|
|
||||
|
## Mandatory T6 Obligations Carried Forward |
||||
|
|
||||
|
The following must be implemented and proven by T6 before W3 can be considered complete: |
||||
|
|
||||
|
### Concurrent company checklist create and limit |
||||
|
|
||||
|
1. Fire two concurrent creates with the same company, same normalized `idempotencyKey`, and same body. Assert exactly one row, both callers receive the same successful item or a safe replay, and neither receives an internal 500. |
||||
|
2. Fire two concurrent creates with the same `idempotencyKey` but different bodies. Assert exactly one row, one successful create, and one 409 `9704`, not a unique-constraint 500. |
||||
|
3. Start with 49 active company items and concurrently create two distinct items. Assert the final active count is exactly 50, one request succeeds, one returns 409 `9703`, and no partial or 51st row remains. |
||||
|
4. Start with 50 active company items and assert new creates return 409 `9703`. Prove inactive items are excluded from the active limit and that replaying an existing idempotency key remains successful without counting as a new item. |
||||
|
5. Repeat the idempotency-key race across two companies and assert `(company_id, idempotency_key)` isolation with no cross-company replay. |
||||
|
|
||||
|
### First progress version and first-create race |
||||
|
|
||||
|
6. When no `student_onboarding_progress` row exists, a request carrying a non-zero `version` must return 409 `9705`, including the first completion path. |
||||
|
7. Exercise simultaneous first progress creation for the same student/item. Assert one row, one safe result, and a deterministic conflict/replay mapping rather than an uncaught unique-constraint 500. |
||||
|
|
||||
|
### First company-knowledge upsert race |
||||
|
|
||||
|
8. Exercise simultaneous first knowledge upserts for the same company. Assert one row, one successful create, and a deterministic `9706` or other contract-approved conflict response; do not allow an uncaught unique-constraint 500. |
||||
|
9. Confirm the same behavior through the HTTP integration tests with the final error/status mapping. |
||||
|
|
||||
|
## Readiness for W3/T6 |
||||
|
|
||||
|
The W2 implementation and review evidence are sufficient to proceed. T6 has the required service surfaces, shared error constants, ownership predicates, and test scaffolding. If a database-level lock becomes necessary to make the 50-item boundary or first-create race deterministic, update the T6 scope/contract explicitly before editing repository or migration files; do not silently widen the file scope. |
||||
|
|
||||
|
## Final Assessment |
||||
|
|
||||
|
**Verdict: PASS** |
||||
|
|
||||
|
W2 is spec-compliant at the service and controller contract level, passes the required evidence suite, preserves existing API semantics, maintains response-field minimization, and provides the necessary interfaces for T6. The unresolved concurrency and first-create-version behavior is correctly deferred to W3/T6 and must not be closed without the obligations above. |
||||
@ -0,0 +1,115 @@ |
|||||
|
# W3/T6 Repair Round 1 Re-Review |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Base:** `ca85ab5` |
||||
|
- **Head:** `97b3b16` |
||||
|
- **Scope:** 4 files, +46/-23 |
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** Approved for the focused repair |
||||
|
- **Original W3-T6 Important finding:** **RESOLVED** |
||||
|
- **Blocking findings:** None |
||||
|
|
||||
|
## Reviewed Inputs |
||||
|
|
||||
|
- Prior failed report: `W3-T6.md` |
||||
|
- Repair brief: `.superpowers/sdd/task-briefs/T6-repair-1.md` |
||||
|
- Scoped diff: `ca85ab5..97b3b16` |
||||
|
- T6 brief, onboarding checklist spec, design, and prior wave reviews where relevant |
||||
|
|
||||
|
## Original Finding Resolution |
||||
|
|
||||
|
The original defect was that the active-item count was derived from a normal snapshot read after the transaction had already established its snapshot. Under MySQL-compatible `REPEATABLE READ`, two concurrent creates could both observe 49 active items and write the 51st row. |
||||
|
|
||||
|
The repair addresses the actual transaction boundary: |
||||
|
|
||||
|
1. `OnboardingChecklistService.createCompanyChecklistItem` now runs at `Isolation.READ_COMMITTED`. |
||||
|
2. It acquires a `PESSIMISTIC_WRITE` company lock before inspecting company items. |
||||
|
3. It reads company checklist rows through `findCompanyItemsForUpdate`, which is also `PESSIMISTIC_WRITE`. |
||||
|
4. Idempotency replay/conflict checks and the active-item count are performed from that locking read, not from an earlier ordinary snapshot. |
||||
|
5. The first writer locks the company row, inserts while holding the lock, and commits. The second writer waits; after acquiring the lock, its locking read observes the committed 50th item and returns `9703`. |
||||
|
|
||||
|
The company-row lock plus locking item read are in the same transaction and use the same lock order as update/delete paths. This makes the 49-to-50 boundary deterministic for the supported write paths. The `PESSIMISTIC_WRITE` query includes inactive rows; active filtering remains in application logic, preserving the soft-delete accounting behavior. |
||||
|
|
||||
|
## Regression Review |
||||
|
|
||||
|
- Idempotent create replay still returns the existing row and performs no second insert. |
||||
|
- Different-body reuse of the same idempotency key still returns `9704`. |
||||
|
- Same idempotency key remains isolated by `company_id`. |
||||
|
- Soft-deleted items remain excluded from the active count. |
||||
|
- Existing rows with an idempotency key remain replayable after soft deletion. |
||||
|
- Company ownership and cross-company predicates are unchanged. |
||||
|
- No controller, DTO, error-code, migration, or user-visible API shape changed in this repair. |
||||
|
- Update/delete lock ordering is consistent with create: company row first, then company-scoped item access. |
||||
|
|
||||
|
## Verification |
||||
|
|
||||
|
### Required focused command |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
$env:JAVA_HOME='D:\java'; $env:Path='D:\java\bin;'+$env:Path |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=OnboardingConcurrencyIntegrationTest,OnboardingCompanyChecklistServiceTest' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 22, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
### Stronger production-isolation reproduction |
||||
|
|
||||
|
The boundary test was also run with `REPEATABLE_READ` forced at the Hikari/connection-default level, separately from the nested test property: |
||||
|
|
||||
|
```powershell |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' ` |
||||
|
'-Dtest=OnboardingConcurrencyIntegrationTest$RepeatableReadLimitBoundary' ` |
||||
|
'-Dspring.datasource.hikari.transaction-isolation=TRANSACTION_REPEATABLE_READ' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
The SQL trace confirms both locking operations were issued: |
||||
|
|
||||
|
```sql |
||||
|
select id from companies where id=? for update |
||||
|
select ... from onboarding_checklist_items |
||||
|
where scope_type='COMPANY' and scope_id=? and company_id=? |
||||
|
order by id for update |
||||
|
``` |
||||
|
|
||||
|
### Full onboarding regression |
||||
|
|
||||
|
```powershell |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=Onboarding*Test,CompanyKnowledge*Test' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 85, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
`git diff --check ca85ab5..97b3b16` passed. No implementation files were modified during this re-review. |
||||
|
|
||||
|
## Remaining Findings |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
1. The earlier W3-T6 minor finding about a few broader concurrency tests over-constraining all callers to succeed under global `REPEATABLE_READ` remains outside this focused repair. The contract permits deterministic `9704`/`9705`/`9706` conflicts. The repaired 49→50 boundary itself now asserts exactly one success and one `9703`. |
||||
|
2. Real MySQL 8.4 execution was not available in this environment. H2 `MODE=MySQL` with forced `REPEATABLE_READ` passes, but the B3 integration gate should still confirm the same boundary on the real database. |
||||
|
3. The prior knowledge-service lock-timeout/deadlock mapping observation remains unchanged and is outside T6 repair round 1. |
||||
|
|
||||
|
None of these block acceptance of the focused repair. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
The original concurrency finding is resolved. The implementation now uses a locking/current read inside the create transaction and the strengthened repeatable-read boundary test passes. No idempotency, soft-delete, cross-company ownership, or API behavior regression was found in the reviewed scope. |
||||
@ -0,0 +1,104 @@ |
|||||
|
# W3/T6 Review: Permissions, Error Codes, Concurrency, and Privacy |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Base:** `a5730a7` |
||||
|
- **Head:** `ca85ab5` |
||||
|
- **Diff:** 6 files, +936/-3 |
||||
|
- **Spec compliance:** ❌ Issues found |
||||
|
- **Task quality:** Needs fixes |
||||
|
- **Blocking findings:** 1 Important |
||||
|
|
||||
|
## Scope Reviewed |
||||
|
|
||||
|
Read the T6 brief, execution contract, checklist and company-knowledge specs, design decisions, reviewer guidance, production diff, and all three added integration-test classes. |
||||
|
|
||||
|
Verified the explicit `/onboarding/**` authenticated matcher, STUDENT/COMPANY service role checks, company ownership predicates, checklist HTTP response whitelists, privacy assertions, optimistic-version mappings, and the placement of pessimistic locks relative to transaction reads. |
||||
|
|
||||
|
## Evidence |
||||
|
|
||||
|
Default focused suite: |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
$env:JAVA_HOME='D:\java' |
||||
|
$env:Path='D:\java\bin;'+$env:Path |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=OnboardingAuthorizationIntegrationTest,OnboardingConcurrencyIntegrationTest,OnboardingPrivacyTest' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 19, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
Production-isolation regression: |
||||
|
|
||||
|
```powershell |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -q '-Dtest=OnboardingConcurrencyIntegrationTest' '-Dspring.datasource.hikari.transaction-isolation=TRANSACTION_REPEATABLE_READ' test |
||||
|
``` |
||||
|
|
||||
|
Result: |
||||
|
|
||||
|
```text |
||||
|
Tests run: 10, Failures: 5, Errors: 0, Skipped: 0 |
||||
|
``` |
||||
|
|
||||
|
The focused 49→50 test failed because both concurrent creates succeeded: |
||||
|
|
||||
|
```text |
||||
|
concurrentCreatesAtLimitBoundaryNeverWriteFiftyFirstItem |
||||
|
Expected size: 1 but was: 2 |
||||
|
``` |
||||
|
|
||||
|
`git diff --check a5730a7..ca85ab5` passed. No implementation files were modified during review. |
||||
|
|
||||
|
## Strengths |
||||
|
|
||||
|
- `SecurityConfig.java:130` explicitly marks `/onboarding/**` as authenticated; anonymous requests return 401 through the configured entry point. |
||||
|
- Student and company role checks are enforced in service methods, not only by token claims. Company ownership is checked through `owner_user_id` before item mutation. |
||||
|
- Cross-company update/delete paths return `9701` without returning the target item or modifying it. |
||||
|
- `OnboardingChecklistService.java:554` maps both optimistic-lock and duplicate-insert failures for progress to `9705`; `CompanyKnowledgeService.java:138` similarly maps knowledge conflicts to `9706`. |
||||
|
- Soft-delete preserves history and student effective-checklist queries filter inactive items. |
||||
|
- Privacy tests use response JSON for several write/read/error paths and assert removal of `ownerUserId`, `studentUserId`, hashes, tokens, and moderation internals. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important |
||||
|
|
||||
|
1. **The 50-active-item guard is not transaction-safe under the production database's default isolation.** `OnboardingChecklistService.java:153` calls `requireCompanyOwner()`, whose normal read at `OnboardingChecklistService.java:256` establishes the transaction snapshot before `lockCompany()` runs at `OnboardingChecklistService.java:155`. The subsequent `findAll()` at `OnboardingChecklistService.java:168` and active count at `OnboardingChecklistService.java:182` are ordinary consistent reads, so a waiting writer can still observe the pre-commit snapshot. With 49 active rows, two concurrent creates can both observe 49, both pass the check, and both insert, producing 51 active items. MySQL defaults to `REPEATABLE READ` and `application.yml` does not override it. The H2 test at `OnboardingConcurrencyIntegrationTest.java:40-41` also does not set the production isolation level, which is why the default run masks the defect. |
||||
|
|
||||
|
Reproduction command: |
||||
|
|
||||
|
```powershell |
||||
|
mvn -q '-Dtest=OnboardingConcurrencyIntegrationTest' '-Dspring.datasource.hikari.transaction-isolation=TRANSACTION_REPEATABLE_READ' test |
||||
|
``` |
||||
|
|
||||
|
The test fails at `OnboardingConcurrencyIntegrationTest.java:111` with two successful `CompanyChecklistItemView` results instead of one success and one `9703` conflict; the seeded 49 rows plus both inserts means the database has 51 active items. |
||||
|
|
||||
|
**Required fix:** make the post-lock idempotency and active-count decisions from locking/current reads, or explicitly run these write transactions at `READ_COMMITTED`. A company-row `PESSIMISTIC_WRITE` lock alone is insufficient when the transaction snapshot was established by a pre-lock ordinary read. Add a regression test that runs the 49→50 boundary under the same isolation model as MySQL or against MySQL 8.4. |
||||
|
|
||||
|
### Minor |
||||
|
|
||||
|
1. Several concurrency tests over-constrain outcomes beyond the T6 contract. `OnboardingConcurrencyIntegrationTest.java:75` requires both same-key callers to succeed, while the task brief permits `9704`; `OnboardingConcurrencyIntegrationTest.java:184` and `OnboardingConcurrencyIntegrationTest.java:202` similarly require all progress/knowledge callers to succeed even though the contract permits deterministic `9705`/`9706` conflict responses. Under `TRANSACTION_REPEATABLE_READ`, those tests fail for contract-permitted outcomes. Assert exactly one successful mutation and one allowed conflict/replay, and reject unexpected exception types. |
||||
|
|
||||
|
2. `CompanyKnowledgeService.java:135-140` only translates optimistic-lock and integrity exceptions. Under the repeatable-read regression run, the same-version knowledge test surfaced an unmapped H2 transaction-rollback/deadlock exception and failed the `9706` assertion at `OnboardingConcurrencyIntegrationTest.java:238-241`. Verify and, if reproducible on MySQL, map lock acquisition/lock-timeout failures at the service boundary to the appropriate `9702`/`9705`/`9706` contract error. |
||||
|
|
||||
|
## Cannot Verify From Diff |
||||
|
|
||||
|
- Real MySQL 8.4 execution was not available in this review. The repeatable-read H2 reproduction is sufficient to reject the current locking design, but the final B3 gate must also run the real MySQL path and assert 50 active items after concurrent boundary creation. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
The permission, ownership, privacy, and default-isolation tests are well structured, and the focused suite is green. The implementation is not ready to accept because its central concurrency guarantee can be violated under the production MySQL default isolation: concurrent creates can write the 51st active checklist item. |
||||
|
|
||||
|
## Receipt |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W3 --base a5730a7 --head ca85ab5 --report changes/n04-onboard/.superpowers/sdd/reviews/W3-T6.md --verdict fail |
||||
|
``` |
||||
@ -0,0 +1,81 @@ |
|||||
|
# W3 T7 Review: Onboarding End-to-End and Gap Tests |
||||
|
|
||||
|
## Verdict |
||||
|
|
||||
|
- **Spec compliance:** PASS |
||||
|
- **Task quality:** Approved |
||||
|
- **Overall:** PASS |
||||
|
- **Base:** `97b3b16` |
||||
|
- **Head:** `73afd87` |
||||
|
- **Blocking findings:** none |
||||
|
- **Cannot verify from this task alone:** real MySQL 8.4 execution remains a later verification-gate obligation. |
||||
|
|
||||
|
## Verification |
||||
|
|
||||
|
- Read `task-reviewer-prompt.md`, `writing-good-tests.md`, the T7 brief, execution contract, both onboarding specs, the full `97b3b16..73afd87` diff, and the complete changed test files where the diff output was truncated. |
||||
|
- Scope check: `git diff --name-only 97b3b16..73afd87` contains no `mydreamjob-server/src/main/**` files. The T7 implementation scope is limited to three test files plus workflow/review artifacts. |
||||
|
- Ran `OnboardingEndToEndIntegrationTest,CompanyKnowledgeServiceTest,OnboardingRepositoryIntegrationTest`: `Tests run: 30, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
- Ran `Onboarding*Test,CompanyKnowledge*Test`: `Tests run: 96, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
- Ran `OnboardingEndToEndIntegrationTest` with JUnit random method ordering: `Tests run: 7, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
- Scoped `git diff --check 97b3b16..73afd87 -- <three T7 test files>` passed. Range-wide `git diff --check` reports two pre-existing blank lines at EOF in `W2-T4.md` and `W2-T5.md`, outside T7. |
||||
|
|
||||
|
## Spec Compliance |
||||
|
|
||||
|
- All nine distinct onboarding paths are exercised through the full Spring context, MockMvc, JWT filter, H2 `MODE=MySQL`, repositories, and DTO serialization: `OnboardingEndToEndIntegrationTest.java:82-212`. |
||||
|
- Student fallback and merged-company behavior are covered: standard-only after soft deletion, `customized=false`, merged standard/company items, source values, completion count, and rate (`:135-167`, `:202-208`). |
||||
|
- Student absolute-state completion behavior is covered end to end: first completion, idempotent replay with a stale version, cancellation clearing `completedAt`, stale-version conflict, persisted row count, and unchanged state after conflict (`:214-264`). |
||||
|
- Company checklist behavior is covered end to end: list, create, idempotent replay, request-hash conflict, 50-active-item boundary, update, soft delete, repeated delete, cross-company update/delete isolation, and state preservation (`:266-364`). |
||||
|
- Company knowledge behavior is covered across `BASIC_ONLY`, `CONFIGURED`, `EMPTY`, exact two-review `INSUFFICIENT`, and `SUFFICIENT`, including absence of aggregates below three reviews (`:366-441`). |
||||
|
- Public-review filtering excludes `HIDDEN` and non-public moderation states from counts, tags, ratings, and dimension aggregates. Repository coverage is explicit at `OnboardingRepositoryIntegrationTest.java:267-301`; the E2E flow also adds hidden/pending records before asserting the visible totals and aggregates (`OnboardingEndToEndIntegrationTest.java:410-440`). |
||||
|
- Anonymous 401, wrong-role 403, and missing-company/item 404 paths are covered across the endpoint families, with database-state assertions after failures (`OnboardingEndToEndIntegrationTest.java:443-543`). |
||||
|
- Response privacy checks run for every success and error response and cover owner/student IDs, moderation reason, request/content/identity hashes, IP/User-Agent, password/token fields, phone, email, and idempotency keys (`OnboardingEndToEndIntegrationTest.java:545-577`). |
||||
|
- The focused service tests cover the exact two-review threshold, all eight dimension summaries, deterministic tag top-ten limiting, and recursively quoted JSON tags (`CompanyKnowledgeServiceTest.java:292-358`). |
||||
|
|
||||
|
## Test Falsifiability |
||||
|
|
||||
|
- The E2E tests assert externally observable HTTP status, response JSON, persisted row state, and cross-company ownership effects. They would fail for incorrect routing, authorization, fallback, merging, version handling, soft-delete behavior, or aggregate filtering. |
||||
|
- The threshold tests independently assert the absence of aggregates at zero, one, and two public reviews, and their presence at three. A change to the threshold or query filtering would fail them. |
||||
|
- The repository test independently verifies `PUBLISHED` plus approved moderation filtering and excludes `HIDDEN` records from all aggregate queries. A filter regression would fail the counts, tags, and dimension sample assertions. |
||||
|
- The privacy helper checks serialized response bodies rather than trusting service DTO construction alone. |
||||
|
- The randomized-order E2E run passed without reliance on wall-clock precision, external services, or test-order state. |
||||
|
|
||||
|
## Strengths |
||||
|
|
||||
|
- The new E2E test uses the real application context and persistence layer, not mocked controllers or repositories. |
||||
|
- Error-path state preservation is asserted at the database level, not only through HTTP status codes. |
||||
|
- The coverage combines a broad endpoint contract flow with focused service/repository tests for threshold, tag, and moderation-filter edge cases. |
||||
|
- No production files were changed; the task stayed within the intended test-hardening scope. |
||||
|
|
||||
|
## Issues |
||||
|
|
||||
|
### Critical (Must Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Important (Should Fix) |
||||
|
|
||||
|
None. |
||||
|
|
||||
|
### Minor (Nice to Have) |
||||
|
|
||||
|
- `OnboardingEndToEndIntegrationTest.java:82-212` combines success checks for all nine endpoints into one long flow. It is correct, but a failure gives a broad diagnosis surface. Splitting the endpoint families into smaller E2E tests would improve failure isolation without changing behavior. |
||||
|
- `OnboardingEndToEndIntegrationTest.java:559-576` checks sensitive fields with raw serialized-string substrings. It covers the named fields in the current contract, but a recursive JSON field-name check would be more robust if DTO serialization changes or sensitive data is nested under a renamed field. |
||||
|
- Tag top-ten limiting and recursively quoted JSON parsing are covered at the service layer (`CompanyKnowledgeServiceTest.java:314-358`), while the E2E flow only exercises the three-tag sufficient case (`OnboardingEndToEndIntegrationTest.java:425-434`). This is acceptable for T7 because the focused behavior is covered, but retaining an HTTP-level edge case would provide broader integration evidence. |
||||
|
- Non-T7 range artifact: `git diff --check 97b3b16..73afd87` reports blank lines at EOF in `W2-T4.md:94` and `W2-T5.md:75`. The three T7 test files themselves pass `git diff --check`. |
||||
|
|
||||
|
## Residual Verification Warning |
||||
|
|
||||
|
- Real MySQL 8.4 behavior has not been run in this environment. H2 `MODE=MySQL` passed; MySQL/Docker black-box verification remains owned by the later verification gate. |
||||
|
- The prior T6 observation about knowledge-service lock-timeout/deadlock exception mapping remains outside T7 and was not modified here. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Task quality:** Approved |
||||
|
|
||||
|
**Reasoning:** The T7 additions provide real full-context coverage for all nine endpoints, the required fallback, idempotency, concurrency-conflict, threshold, filtering, authorization, privacy, and state-preservation behaviors. The focused and full required suites, plus randomized ordering, all pass, and no production files were changed. |
||||
|
|
||||
|
Controller receipt command when `ssf` is available: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W3 --base 97b3b16 --head 73afd87 --report changes/n04-onboard/.superpowers/sdd/reviews/W3-T7.md --verdict pass |
||||
|
``` |
||||
@ -0,0 +1,83 @@ |
|||||
|
# W3-T8 Re-review: OpenAPI 示例与默认值修复 |
||||
|
|
||||
|
- Wave: W3 |
||||
|
- Task: T8 |
||||
|
- Review round: repair round 1 |
||||
|
- Base SHA: `aad0ba6` |
||||
|
- Head SHA: `310d931` |
||||
|
- Prior failed report: `W3-T8.md` |
||||
|
- Repair brief: `T8-repair-1.md` |
||||
|
- Verdict: **PASS** |
||||
|
|
||||
|
## Original Findings |
||||
|
|
||||
|
### Resolved: knowledge Upsert response example omitted `version` |
||||
|
|
||||
|
`docs/openapi-rc.json:7439` now includes `data.version: 2` in the `PUT /onboarding/company/knowledge` 200 example. The request example uses `version: 1`, so the returned `version: 2` is consistent with update semantics. `CompanyKnowledgeView` still exposes `version`, and the schema remains unchanged. |
||||
|
|
||||
|
### Resolved: create-item schema omitted runtime defaults |
||||
|
|
||||
|
`docs/openapi-rc.json:11718` now declares `sortOrder.default = 1000`. |
||||
|
`docs/openapi-rc.json:11724` now declares `required.default = false`. |
||||
|
These match the initialized runtime fields in `CompanyChecklistItemCreateRequest`. |
||||
|
|
||||
|
### Resolved: verifier did not assert the repaired contract |
||||
|
|
||||
|
`docs/verify_openapi.py:300-309` now asserts: |
||||
|
|
||||
|
- `CompanyChecklistItemCreateRequest.sortOrder.default == 1000` |
||||
|
- `CompanyChecklistItemCreateRequest.required.default is False` |
||||
|
- knowledge Upsert 200 example `data.version == 2` |
||||
|
|
||||
|
The verifier diff adds 12 lines and removes none. Existing path, operation, schema, security, response-reference, field, and error-code checks remain intact, so the prior checks were not weakened. |
||||
|
|
||||
|
## Scope Verification |
||||
|
|
||||
|
The scoped semantic OpenAPI diff contains exactly the three intended changes: |
||||
|
|
||||
|
- `CompanyChecklistItemCreateRequest.required.default`: added `false` |
||||
|
- `CompanyChecklistItemCreateRequest.sortOrder.default`: added `1000` |
||||
|
- knowledge Upsert 200 example `data.version`: added `2` |
||||
|
|
||||
|
No runtime Java, test, migration, endpoint, schema, security, or unrelated documentation changes are present in `aad0ba6..310d931`. |
||||
|
|
||||
|
## Evidence |
||||
|
|
||||
|
```text |
||||
|
python .\docs\verify_openapi.py |
||||
|
paths: 135 |
||||
|
N04 onboarding endpoints and schemas present (9 operations / 6 paths) |
||||
|
No empty ref keys found |
||||
|
Valid JSON: True |
||||
|
All checks passed! |
||||
|
``` |
||||
|
|
||||
|
```text |
||||
|
mvn -Dtest=Onboarding*Test,CompanyKnowledge*Test test |
||||
|
Tests run: 96, Failures: 0, Errors: 0, Skipped: 0 |
||||
|
BUILD SUCCESS |
||||
|
``` |
||||
|
|
||||
|
`git diff --check aad0ba6..310d931` passed. |
||||
|
|
||||
|
## Remaining Findings |
||||
|
|
||||
|
No blocking findings. |
||||
|
|
||||
|
Non-blocking carried-forward findings from the prior review remain outside this focused repair: |
||||
|
|
||||
|
- Error-code verification remains partly text-based rather than asserting exact operation/status/example structure. |
||||
|
- Success responses continue to use the repository's existing `*/*` media type instead of explicit `application/json`. |
||||
|
- Full `mvn clean verify` and real MySQL 8.4 / Docker acceptance remain pending; the previously identified full-suite failures are unrelated to N04/T8. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Ready for T8 receipt:** Yes |
||||
|
|
||||
|
The two original Important findings are resolved, and both are now protected by exact verifier assertions without weakening earlier checks. |
||||
|
|
||||
|
Receipt command: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W3-T8 --base aad0ba6 --head 310d931 --report .superpowers/sdd/reviews/W3-T8-rereview.md --verdict pass |
||||
|
``` |
||||
@ -0,0 +1,78 @@ |
|||||
|
# W3-T8 Review: OpenAPI、接口文档与前端联调 |
||||
|
|
||||
|
- Wave: W3 |
||||
|
- Task: T8 |
||||
|
- Base SHA: `73afd87` |
||||
|
- Head SHA: `aad0ba6` |
||||
|
- Review report: `.superpowers/sdd/reviews/W3-T8.md` |
||||
|
- Verdict: **FAIL** |
||||
|
|
||||
|
## Strengths |
||||
|
|
||||
|
- T8 的交付范围正确:diff 仅涉及 OpenAPI、文档、验证脚本和 SDD 状态文件,没有修改 Java 生产代码或测试代码。 |
||||
|
- 与 `73afd87` 相比,OpenAPI 精确新增 6 个 `/onboarding` 路径、9 个 operation、20 个 schema;原有 129 个路径和 139 个 schema 的结构化内容均未变化。 |
||||
|
- 9 个 operation 的 `operationId` 唯一,全部声明 `bearerAuth`;成功响应引用正确;9701-9706 均出现在对应错误响应中。 |
||||
|
- `docs/onboarding-api.md` 覆盖角色、企业所有权、通用/企业清单合并、完成状态与版本、幂等创建、软删除、知识回退、3 条评价阈值、错误码和请求/响应示例。 |
||||
|
- `docs/frontend-integration.md` 第 22 节覆盖 Base URL、鉴权、9 个接口、请求示例、状态回退及 400/401/403/404/409 处理。 |
||||
|
- README 和 roadmap 已更新为 N04“实现完成,待真实 MySQL/Docker 验收”,并准确记录当前全量测试失败情况。 |
||||
|
- `docs/verify_openapi.py` 是增强而非削弱:删除了旧的固定路径总数断言,改为更强的 135 paths / 172 operations 断言,并新增 N04 路径、operation、security、schema 和错误码检查。 |
||||
|
- N04 专项回归通过:`Tests run: 96, Failures: 0, Errors: 0, Skipped: 0`,`BUILD SUCCESS`。 |
||||
|
|
||||
|
## Blocking Findings |
||||
|
|
||||
|
### Important: OpenAPI 示例与运行时响应不一致 |
||||
|
|
||||
|
- File: `docs/openapi-rc.json:7430-7439` |
||||
|
- `PUT /onboarding/company/knowledge` 的 200 示例 `data` 缺少 `version`。 |
||||
|
- Runtime `CompanyKnowledgeView`、OpenAPI schema 以及 `docs/onboarding-api.md` 都明确包含 `version`;企业客户端必须使用该字段进行下一次乐观锁更新。 |
||||
|
- 因此该示例会误导前端在对更新响应建模或联调时遗漏版本,直接违反 T8“字段、默认值与运行时 DTO 完全一致”的要求。 |
||||
|
- Fix: 在 200 示例中补充一个与更新语义一致的 `version`,并在 `docs/verify_openapi.py` 中增加示例字段断言。 |
||||
|
|
||||
|
### Important: 创建请求的 OpenAPI schema 未表达运行时默认值 |
||||
|
|
||||
|
- File: `docs/openapi-rc.json:11714-11722` |
||||
|
- `CompanyChecklistItemCreateRequest.sortOrder` 缺少 `default: 1000`。 |
||||
|
- `CompanyChecklistItemCreateRequest.required` 缺少 `default: false`。 |
||||
|
- Runtime DTO 明确初始化了这两个默认值,文档正文也说明默认值,但静态 OpenAPI schema 没有表达,违反 T8 对默认值一致性的要求。 |
||||
|
- Fix: 在对应 schema 属性中加入 `default`,并在 `docs/verify_openapi.py` 中断言这些默认值。 |
||||
|
|
||||
|
## Minor Findings |
||||
|
|
||||
|
- `docs/verify_openapi.py` 对 9704 的检查只验证响应描述或示例的序列化文本中存在该错误码;当前 9704 只出现在描述中。建议按 operation/status 精确断言错误码和示例结构,同时覆盖 `version` 默认值和响应示例,避免回归。 |
||||
|
- OpenAPI 200 响应当前沿用仓库既有的 `*/*` content type,而不是更明确的 `application/json`。这不影响本次 T8 通过判定,但会给前端代码生成带来额外歧义。 |
||||
|
- `changes/n04-onboard/.superpowers/sdd/progress.md` 尚未记录 T8 独立 review 结果,符合当前“review pending”状态,等待 receipt 后再更新。 |
||||
|
|
||||
|
## Non-Blocking Verification |
||||
|
|
||||
|
- `python .\docs\verify_openapi.py`: |
||||
|
- `paths: 135` |
||||
|
- `N04 onboarding endpoints and schemas present (9 operations / 6 paths)` |
||||
|
- `All checks passed!` |
||||
|
- 结构化对比 `73afd87..aad0ba6`: |
||||
|
- added paths: 6 |
||||
|
- added operations: 9 |
||||
|
- added schemas: 20 |
||||
|
- existing paths changed: 0 |
||||
|
- existing schemas changed: 0 |
||||
|
- N04 regression: |
||||
|
- `mvn -Dtest=Onboarding*Test,CompanyKnowledge*Test test` |
||||
|
- `Tests run: 96, Failures: 0, Errors: 0, Skipped: 0` |
||||
|
- `BUILD SUCCESS` |
||||
|
- Reported full-suite failure set reproduced independently: |
||||
|
- `mvn -Dtest=CareerProgressServiceTest,JobReviewDimensionControllerTest,JobReviewDimensionServiceTest,LocalResumeStorageServiceTest test` |
||||
|
- `Tests run: 69, Failures: 2, Errors: 9` |
||||
|
- failures are the reported CareerProgress/JobReviewDimension assertions and LocalResumeStorage temp-directory cleanup errors. |
||||
|
- `git diff --check 73afd87..aad0ba6` passed. |
||||
|
- `git diff --name-status 73afd87..aad0ba6` contains no Java production or test changes, so the reported failures are unrelated to the T8 commit. The N04 branch's only changes touching those packages are additive `JobReview` repository query methods that are not used by the failing paths; CareerProgress and LocalResumeStorage code is unchanged. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**Ready to merge?** No |
||||
|
|
||||
|
**Reasoning:** The OpenAPI structure, documentation coverage, security declarations, error-code coverage, and N04 regression are strong, but the static contract is not yet fully runtime-consistent. The missing knowledge-response `version` example and missing create-request defaults are actionable contract defects that must be fixed and re-reviewed before T8 can pass. |
||||
|
|
||||
|
Receipt command: |
||||
|
|
||||
|
```bash |
||||
|
ssf execution review changes/n04-onboard --wave W3-T8 --base 73afd87 --head aad0ba6 --report .superpowers/sdd/reviews/W3-T8.md --verdict fail |
||||
|
``` |
||||
@ -0,0 +1,113 @@ |
|||||
|
# W3 Wave Review: n04-onboard |
||||
|
|
||||
|
- Wave: `W3` |
||||
|
- Base SHA: `a5730a7` |
||||
|
- Head SHA: `310d931` |
||||
|
- Verdict: **PASS (conditional)** |
||||
|
- Blocking findings inside W3: **None** |
||||
|
- Release status: **blocked by external verification gates** |
||||
|
|
||||
|
## Scope Reviewed |
||||
|
|
||||
|
Read the execution contract, onboarding-checklist and company-knowledge specs, design, W3 task reports (`W3-T6.md`, `W3-T6-rereview.md`, `W3-T7.md`, `W3-T8.md`, `W3-T8-rereview.md`), task briefs where needed, the combined diff `a5730a7..310d931`, and the relevant production, integration-test, OpenAPI, and documentation artifacts. |
||||
|
|
||||
|
The combined diff contains 31 files, `+7123/-1903`. W3 production changes are limited to: |
||||
|
|
||||
|
- `SecurityConfig.java` |
||||
|
- `OnboardingChecklistItemRepository.java` |
||||
|
- `OnboardingChecklistService.java` |
||||
|
- `CompanyKnowledgeService.java` |
||||
|
|
||||
|
The remainder is onboarding integration/privacy/end-to-end testing, OpenAPI/docs, and SDD workflow artifacts. No V1-V38 migration is modified in W3. No existing API path, request/response shape, or error semantics are intentionally changed. |
||||
|
|
||||
|
## Cross-Task Integration |
||||
|
|
||||
|
### Permissions and ownership |
||||
|
|
||||
|
- `/onboarding/**` is explicitly authenticated; anonymous requests return 401 through the existing security entry point. |
||||
|
- STUDENT and COMPANY role checks are enforced in service code, not merely by the HTTP matcher. |
||||
|
- Company operations resolve the company from `owner_user_id` and reject cross-company checklist mutation with `9701`/404 without modifying the target row. |
||||
|
- Privacy assertions cover success and error responses and verify absence of internal IDs, hashes, moderation fields, tokens, IP/User-Agent, phone, and email fields. |
||||
|
|
||||
|
### Concurrency and isolation |
||||
|
|
||||
|
- Company checklist creation runs at `READ_COMMITTED`, acquires a `PESSIMISTIC_WRITE` company-row lock, and uses a locking read for company item lookup before idempotency and active-count decisions. |
||||
|
- The 49-to-50 boundary is deterministic under H2 `MODE=MySQL` with the connection default forced to `REPEATABLE_READ`; the test observes exactly one success and one `9703`, with 50 active rows. |
||||
|
- Progress updates serialize per student user through a pessimistic user lock; concurrent first creation produces one progress row. |
||||
|
- Knowledge upserts serialize on the owning company row and map optimistic/duplicate-key conflicts to `9706`. |
||||
|
- The focused concurrency suite passes. Real MySQL 8.4 lock/deadlock behavior remains an external acceptance gate. |
||||
|
|
||||
|
### End-to-end contract |
||||
|
|
||||
|
- T7 exercises all 9 onboarding operations through the real Spring context, JWT/security chain, MockMvc, H2 persistence, and DTO serialization. |
||||
|
- Coverage includes standard/company checklist merge and fallback, completion and cancellation, stale versions, idempotent replay and hash conflict, 50-item limit, soft delete/repeat delete, cross-company isolation, `BASIC_ONLY`/`CONFIGURED`, `EMPTY`/`INSUFFICIENT`/`SUFFICIENT`, public-review filtering, tag limits, permission failures, and privacy assertions. |
||||
|
- T8 OpenAPI contains exactly 6 onboarding paths and 9 operations, all with bearer security and the expected response/error references. The repaired examples/defaults match runtime behavior. |
||||
|
|
||||
|
## Verification Evidence |
||||
|
|
||||
|
### Required focused regression |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
$env:JAVA_HOME='D:\java'; $env:Path='D:\java\bin;'+$env:Path |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=Onboarding*Test,CompanyKnowledge*Test' test |
||||
|
``` |
||||
|
|
||||
|
Result: `Tests run: 96, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
|
||||
|
### Production-isolation concurrency check |
||||
|
|
||||
|
```powershell |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=OnboardingConcurrencyIntegrationTest$RepeatableReadLimitBoundary' '-Dspring.datasource.hikari.transaction-isolation=TRANSACTION_REPEATABLE_READ' test |
||||
|
``` |
||||
|
|
||||
|
Result: `Tests run: 1, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
|
||||
|
### Randomized end-to-end ordering |
||||
|
|
||||
|
```powershell |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=OnboardingEndToEndIntegrationTest' '-Djunit.jupiter.testmethod.order.default=org.junit.jupiter.api.MethodOrderer$Random' test |
||||
|
``` |
||||
|
|
||||
|
Result: `Tests run: 7, Failures: 0, Errors: 0, Skipped: 0`, `BUILD SUCCESS`. |
||||
|
|
||||
|
### OpenAPI verification |
||||
|
|
||||
|
```powershell |
||||
|
python docs\verify_openapi.py |
||||
|
``` |
||||
|
|
||||
|
Result: `paths: 135`; `N04 onboarding endpoints and schemas present (9 operations / 6 paths)`; `No empty ref keys found`; `Valid JSON: True`; `All checks passed!`. |
||||
|
|
||||
|
Structured checks found 0 removed paths, 6 added paths, 0 removed schemas, and 20 added schemas. |
||||
|
|
||||
|
## External Blockers |
||||
|
|
||||
|
The reported full-suite failures were independently reproduced in isolation: |
||||
|
|
||||
|
```powershell |
||||
|
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=CareerProgressServiceTest,JobReviewDimensionControllerTest,JobReviewDimensionServiceTest,LocalResumeStorageServiceTest' test |
||||
|
``` |
||||
|
|
||||
|
Result: `Tests run: 69, Failures: 2, Errors: 9, Skipped: 0`, `BUILD FAILURE`. |
||||
|
|
||||
|
- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries` fails with expected `1` but got `0`. |
||||
|
- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores` fails with 500 due a null `Page`. |
||||
|
- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1` fails with null `Page.getContent()`. |
||||
|
- `LocalResumeStorageServiceTest` reports 8 JUnit extension cleanup errors caused by sandbox `AccessDeniedException` on temporary directories. |
||||
|
|
||||
|
These failures are outside the W3 review range. `git diff --name-only a5730a7..310d931` contains no files in the failing controllers/services/storage paths. Across the full N04 branch from `a2c66b3`, the only review-package changes are additive query methods in `JobReviewRepository` and `JobReviewDimensionScoreRepository`; the failing CareerProgress and LocalResumeStorage code is unchanged. Therefore W3 itself can pass conditionally, but final release cannot be declared while `mvn clean verify` is red. |
||||
|
|
||||
|
Real MySQL 8.4/Docker execution also remains outstanding. The contract requires it for the final completion proof, and it was not performed without separate authorization. |
||||
|
|
||||
|
## Minor Findings |
||||
|
|
||||
|
- `git diff --check a5730a7..310d931` reports two workflow-report files with blank lines at EOF (`W2-T4.md`, `W2-T5.md`). This is documentation-only and non-blocking. |
||||
|
- Some OpenAPI error-code assertions remain partly textual rather than exact response-shape assertions. The repaired defaults/examples are covered. |
||||
|
- H2 `MODE=MySQL` is strong regression coverage but is not a substitute for the pending real-database acceptance gate. |
||||
|
|
||||
|
## Assessment |
||||
|
|
||||
|
**W3 passes conditionally.** T6 concurrency repair, T7 end-to-end coverage, and T8 contract/docs repair are integrated and verified; no blocking W3 finding remains. The final release/DP-7 gate remains blocked by the independently reproduced non-N04 full-suite failures and the pending real MySQL 8.4/Docker acceptance run. |
||||
|
|
||||
|
Report path: `D:\1\zhi\zhilai\changes\n04-onboard\.superpowers\sdd\reviews\W3-wave.md` |
||||
@ -0,0 +1,50 @@ |
|||||
|
# T1 任务简报:V38/V39 迁移与通用清单种子 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W1 |
||||
|
- Depends on: 无 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Specs: `changes/n04-onboard/specs/onboarding-checklist/spec.md`、`changes/n04-onboard/specs/company-knowledge/spec.md` |
||||
|
- Design: `changes/n04-onboard/design.md` 的 Data Model 与 Architecture 部分 |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
新增 V38 建表迁移和 V39 通用清单种子,并先用失败测试锁定表结构、约束、通用项数量和稳定排序。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/main/resources/db/migration/V38__onboarding_checklist_and_company_knowledge.sql` |
||||
|
- `mydreamjob-server/src/main/resources/db/migration/V39__onboarding_standard_checklist_seed.sql` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingMigrationTest.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingSeedDataIntegrationTest.java` |
||||
|
- 如项目迁移测试需要共享测试配置,仅可修改同一测试包内的辅助文件。 |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- 创建 `onboarding_checklist_items`、`student_onboarding_progress`、`company_knowledge`。 |
||||
|
- `onboarding_checklist_items` 至少包含合同规定的字段、唯一键、活动/排序索引、`company_id` 对 `companies(id)` 的外键。 |
||||
|
- `student_onboarding_progress` 必须对学生与条目唯一,并建立 `users(id)`、`onboarding_checklist_items(id)` 外键。 |
||||
|
- `company_knowledge.company_id` 唯一,并建立 `companies(id)` 外键。 |
||||
|
- V39 必须写入稳定的通用清单;每一步使用明确 `item_key`、分类、排序和必填标记,不得使用随机值。 |
||||
|
- 迁移必须兼容 H2 `MODE=MySQL` 与 MySQL 8.4。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写 `OnboardingMigrationTest`、`OnboardingSeedDataIntegrationTest`,确认因表或种子不存在而失败。 |
||||
|
2. 再写 V38/V39 让测试通过。 |
||||
|
3. 不得先写迁移再补测试。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=OnboardingMigrationTest,OnboardingSeedDataIntegrationTest test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 两条证据测试通过。 |
||||
|
- 测试断言三张表存在、关键唯一约束/外键/索引可验证、通用种子数量大于 0 且排序稳定。 |
||||
|
- 不修改 V1-V37。 |
||||
|
- 不创建 W2 实体、Repository、Service 或 Controller。 |
||||
@ -0,0 +1,53 @@ |
|||||
|
# T2 任务简报:Onboarding 实体、Repository 与评价聚合查询 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W1 |
||||
|
- Depends on: T1 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Design: `changes/n04-onboard/design.md` 的 Architecture、Data Model、Decisions |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
在 V38/V39 表结构之上新增实体、Repository 和公司评价聚合查询,先写持久化失败测试,再实现最小映射。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItem.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgress.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledge.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItemRepository.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgressRepository.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeRepository.java` |
||||
|
- 必要时只读扩展 `mydreamjob-server/src/main/java/com/mydreamjob/review/JobReviewRepository.java` 和 `JobReviewDimensionScoreRepository.java` 的公司级聚合查询。 |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingRepositoryIntegrationTest.java` |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- `OnboardingChecklistItem` 映射 `onboarding_checklist_items`,含 `scopeType`、`scopeId`、`companyId`、`itemKey`、title、description、category、sortOrder、required、active、idempotencyKey、requestHash、version、审计时间。 |
||||
|
- `StudentOnboardingProgress` 映射 `student_onboarding_progress`,含 studentUserId、itemId、completed、completedAt、version、审计时间。 |
||||
|
- `CompanyKnowledge` 映射 `company_knowledge`,含 companyId、culture、workPolicies、onboardingGuide、version、审计时间。 |
||||
|
- 清单 Repository 支持按 `companyId` 查询活动通用项+活动企业项,并按 `sortOrder ASC, id ASC` 稳定排序。 |
||||
|
- 进度 Repository 支持按学生和多个 itemId 批量读取,并按学生+条目唯一查询。 |
||||
|
- 知识 Repository 支持按 companyId 查询。 |
||||
|
- 评价聚合查询支持公司下全部 `PUBLISHED` 且 `AUTO_APPROVED/APPROVED` 的评价评分分布、tags 原始列和八维汇总;不得逐岗位或逐评价产生 N+1。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写 `OnboardingRepositoryIntegrationTest`,确认新实体/Repository 不存在或查询失败。 |
||||
|
2. 再实现最小实体和 Repository 使测试通过。 |
||||
|
3. 不创建 Service、Controller、DTO 或业务状态机。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=OnboardingRepositoryIntegrationTest test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 新实体与三张表映射通过 Hibernate `ddl-auto=validate`。 |
||||
|
- 合并查询只返回活动通用项和当前企业活动项。 |
||||
|
- 多学生、多企业、软删除项和排序测试通过。 |
||||
|
- 公司评分/tags/八维聚合仅统计公开评价。 |
||||
@ -0,0 +1,56 @@ |
|||||
|
# T3 任务简报:学生有效清单与完成状态 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W1 |
||||
|
- Depends on: T2 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Specs: `changes/n04-onboard/specs/onboarding-checklist/spec.md` |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
实现 STUDENT 读取有效入职清单和更新本人完成状态的 Service、DTO 与 Controller,先用失败测试锁定回退、合并排序、完成率和幂等语义。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistService.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistController.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/ChecklistItemView.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/EffectiveChecklistResponse.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/ProgressUpdateRequest.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/ProgressUpdateResponse.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingChecklistServiceTest.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingChecklistControllerTest.java` |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- `GET /api/v1/onboarding/companies/{companyId}/checklist` 仅服务 STUDENT。 |
||||
|
- 企业没有活动自定义项时返回通用清单并设置 `customized=false`;有自定义项时返回通用项+企业项并按 `sortOrder ASC, id ASC` 排序。 |
||||
|
- 响应包含 companyId、companyName、customized、totalItems、completedItems、completionRate 和 items。 |
||||
|
- item 包含 itemId、source、itemKey、title、description、category、required、sortOrder、active、completed、completedAt、progressVersion。 |
||||
|
- `PUT /api/v1/onboarding/companies/{companyId}/checklist/items/{itemId}/completion` 设置绝对完成状态。 |
||||
|
- 首次完成写入 completedAt;取消完成清空 completedAt。 |
||||
|
- 相同状态重复提交必须幂等成功,不增加数据库记录。 |
||||
|
- 状态不同且提供过期 version 时返回 `ONBOARDING_PROGRESS_CONCURRENT_UPDATE`。 |
||||
|
- item 必须属于该企业的活动项或活动通用项,否则返回 `ONBOARDING_CHECKLIST_ITEM_NOT_FOUND`。 |
||||
|
- 企业不存在返回 `COMPANY_NOT_FOUND`;非 STUDENT 返回 `FORBIDDEN`。 |
||||
|
- 完成率保留一位小数,空清单为 0.0,最大 100.0。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写 Service 和 Controller 失败测试,确认 RED。 |
||||
|
2. 再实现最小 DTO、Service、Controller 使测试 GREEN。 |
||||
|
3. 不实现企业自定义项 CRUD 或公司知识接口。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=OnboardingChecklistServiceTest,OnboardingChecklistControllerTest test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 测试覆盖通用回退、企业合并、稳定排序、首次完成、重复相同状态、取消完成、版本冲突、跨企业 itemId 和企业不存在。 |
||||
|
- 响应不泄露内部用户字段。 |
||||
|
- 不修改既有已验收接口。 |
||||
@ -0,0 +1,53 @@ |
|||||
|
# T4 任务简报:企业自定义清单 CRUD 与幂等 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W2 |
||||
|
- Depends on: T3 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Specs: `changes/n04-onboard/specs/onboarding-checklist/spec.md` |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
在已有学生清单服务与 Controller 上增加 COMPANY 自定义清单 CRUD,先用失败测试锁定所有权、幂等和乐观锁。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistService.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistController.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/CompanyChecklistItemCreateRequest.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/CompanyChecklistItemUpdateRequest.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/CompanyChecklistItemView.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingCompanyChecklistServiceTest.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingCompanyChecklistControllerTest.java` |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- `GET /api/v1/onboarding/company/checklist-items` 仅 COMPANY,读取本企业全部自定义项,含软删除项与 version。 |
||||
|
- `POST /api/v1/onboarding/company/checklist-items` 创建本企业项,校验 title 1-120、description <=1000、category 枚举、sortOrder 0-9999、required 默认 false、idempotencyKey <=64。 |
||||
|
- 相同企业 + 相同 idempotencyKey + 相同规范化请求体返回原条目;不同请求体返回 `ONBOARDING_IDEMPOTENCY_CONFLICT`。 |
||||
|
- 活动自定义项达到 50 条时返回 `ONBOARDING_ITEM_LIMIT_EXCEEDED`。 |
||||
|
- `PUT /api/v1/onboarding/company/checklist-items/{itemId}` 更新标题、说明、分类、排序、必填和 active,必须校验 version。 |
||||
|
- 过期 version 返回 `ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE`。 |
||||
|
- `DELETE /api/v1/onboarding/company/checklist-items/{itemId}?version={version}` 软删除;重复删除已非活动项幂等成功。 |
||||
|
- 所有企业操作仅限当前 COMPANY 拥有的企业,跨企业 itemId 返回 `ONBOARDING_CHECKLIST_ITEM_NOT_FOUND`。 |
||||
|
- 企业不存在返回 `COMPANY_NOT_FOUND`,非 COMPANY 返回 `FORBIDDEN`。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写 Service/Controller 失败测试并确认 RED。 |
||||
|
2. 再实现最小 DTO、Service、Controller 使测试 GREEN。 |
||||
|
3. 不实现公司知识接口。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=OnboardingCompanyChecklistServiceTest,OnboardingCompanyChecklistControllerTest test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 覆盖查询、创建、幂等重放、幂等冲突、上限、更新、版本冲突、软删除、重复删除和跨企业隔离。 |
||||
|
- 创建请求哈希可重复,不依赖随机或当前时间。 |
||||
|
- 既有学生清单行为不回归。 |
||||
@ -0,0 +1,57 @@ |
|||||
|
# T5 任务简报:公司知识配置与公开评价摘要 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W2 |
||||
|
- Depends on: T4 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Specs: `changes/n04-onboard/specs/company-knowledge/spec.md` |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
实现 COMPANY 知识配置读取/Upsert 和 STUDENT 公司知识聚合接口,先用失败测试锁定基础信息回退、小样本保护和八维摘要。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeService.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeController.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/CompanyKnowledgeUpdateRequest.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/CompanyKnowledgeView.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/CompanyKnowledgeOverviewResponse.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/dto/ReviewSummaryView.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/CompanyKnowledgeServiceTest.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/CompanyKnowledgeControllerTest.java` |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- `GET /api/v1/onboarding/company/knowledge` 仅 COMPANY;无记录返回 configured=false、空内容、version=0;有记录返回 configured=true、culture、workPolicies、onboardingGuide、updatedAt、version。 |
||||
|
- `PUT /api/v1/onboarding/company/knowledge` 仅 COMPANY;首次创建允许无 version;已有记录必须提供当前 version;冲突返回 `ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE`。 |
||||
|
- 知识字段按纯文本处理,culture <=2000,workPolicies <=4000,onboardingGuide <=4000,至少一个非空。 |
||||
|
- `GET /api/v1/onboarding/companies/{companyId}/knowledge` 仅 STUDENT;企业不存在返回 `COMPANY_NOT_FOUND`。 |
||||
|
- 无知识记录时 knowledgeState=BASIC_ONLY、knowledge=null,并返回企业基础资料白名单。 |
||||
|
- 有知识记录时 knowledgeState=CONFIGURED。 |
||||
|
- 评价只统计该公司全部岗位下 PUBLISHED 且 AUTO_APPROVED/APPROVED 的评价。 |
||||
|
- 0 条:state=EMPTY,totalReviews=0,平均分/分布/标签/维度汇总为空。 |
||||
|
- 1-2 条:state=INSUFFICIENT,返回 totalReviews,不返回任何均值。 |
||||
|
- 至少 3 条:state=SUFFICIENT,返回 averageRating、ratingDistribution、topTags(次数降序、字典序升序,最多 10)、八个维度的 averageScore/sampleCount/dataState。 |
||||
|
- 缺失维度 sampleCount=0、dataState=INSUFFICIENT。 |
||||
|
- 响应不得包含 ownerUserId、studentUserId、审核原因、哈希、IP、User-Agent。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写 Service/Controller 失败测试,确认 RED。 |
||||
|
2. 再实现最小 DTO、Service、Controller 让测试 GREEN。 |
||||
|
3. 不修改既有评价/企业接口响应。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=CompanyKnowledgeServiceTest,CompanyKnowledgeControllerTest test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 覆盖 COMPANY 无记录/已有记录/创建/更新/版本冲突。 |
||||
|
- 覆盖 STUDENT BASIC_ONLY、CONFIGURED、EMPTY、INSUFFICIENT、SUFFICIENT。 |
||||
|
- 覆盖公开审核过滤、标签排序、八维缺失和数据最小化。 |
||||
@ -0,0 +1,30 @@ |
|||||
|
# T6 聚焦修复 1:50 条并发边界 |
||||
|
|
||||
|
## Prior Failure |
||||
|
|
||||
|
`W3-T6.md` 在 MySQL 默认 `REPEATABLE READ` 等价测试下复现:49 条活动自定义项时并发发起两个创建请求,两个请求都通过计数检查,最终写入第 51 条,预期为一个成功、另一个 9703。 |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
修复并发 50 条边界,保证在 MySQL 默认隔离级别下最多只能有 50 条活动企业自定义项。 |
||||
|
|
||||
|
## Required Changes |
||||
|
|
||||
|
- 在检查活动条目前使用当前读或锁定读,不得依赖事务早期快照。 |
||||
|
- 企业行锁与活动条目计数必须处于同一事务,并确保计数读取看到锁等待前的已提交结果。 |
||||
|
- 允许使用 `PESSIMISTIC_WRITE` 查询公司自定义项、调整锁顺序或等效数据库级机制。 |
||||
|
- 保留既有的幂等重放、9704、软删除和跨企业隔离行为。 |
||||
|
- 将并发边界测试配置为重复读隔离级别,使其能在 H2 `MODE=MySQL` 下复现并验证 MySQL 风险。 |
||||
|
|
||||
|
## Evidence |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=OnboardingConcurrencyIntegrationTest,OnboardingCompanyChecklistServiceTest test |
||||
|
``` |
||||
|
|
||||
|
## Repair Constraints |
||||
|
|
||||
|
- 只做聚焦修复,不扩展接口或用户可见行为。 |
||||
|
- 不修改 V1-V38。 |
||||
|
- 修复后必须通过 `Onboarding*Test,CompanyKnowledge*Test`。 |
||||
@ -0,0 +1,55 @@ |
|||||
|
# T6 任务简报:权限、错误码、并发与幂等集成 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W3 |
||||
|
- Depends on: T5 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
- Design: `changes/n04-onboard/design.md` 的 API、Error Codes、Risks |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
把 W2 已完成的清单与知识服务接入统一安全、错误码和并发语义,先用集成测试锁定权限矩阵、所有权隔离、版本冲突和响应最小化。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/config/SecurityConfig.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/common/exception/ErrorCode.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistService.java` |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeService.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingAuthorizationIntegrationTest.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingConcurrencyIntegrationTest.java` |
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingPrivacyTest.java` |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- `/onboarding/**` 必须认证;未登录 401。 |
||||
|
- 学生接口仅 STUDENT;企业接口仅 COMPANY;角色不符 403。 |
||||
|
- 企业接口只能操作当前用户绑定的企业;跨企业 itemId 返回 9701,不能泄露其他企业数据。 |
||||
|
- 新增 9701-9706:item not found、item concurrent update、item limit exceeded、idempotency conflict、progress concurrent update、knowledge concurrent update。 |
||||
|
- 创建幂等冲突返回 9704;活动项上限返回 9703。 |
||||
|
- 更新/删除清单项乐观锁冲突映射 9702;进度冲突映射 9705;知识冲突映射 9706。 |
||||
|
- 所有响应使用白名单 DTO,不包含 ownerUserId、studentUserId、审核原因、哈希、IP、User-Agent、Token。 |
||||
|
- 失败请求不得产生部分写入或修改其他企业数据。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写三个集成测试并确认 RED。 |
||||
|
2. 再修改安全配置、错误码和 Service 异常映射使测试 GREEN。 |
||||
|
3. 不改变既有非 onboarding 路径的安全语义。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=OnboardingAuthorizationIntegrationTest,OnboardingConcurrencyIntegrationTest,OnboardingPrivacyTest test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 权限矩阵覆盖匿名、STUDENT、COMPANY、ADMIN、错误企业。 |
||||
|
- 并发与幂等冲突均返回契约规定的 HTTP 状态和错误码。 |
||||
|
- 隐私测试检查 JSON 响应不含内部字段。 |
||||
|
- onboarding 专项测试全部通过。 |
||||
|
- 并发创建:相同 idempotencyKey 并发请求只能产生一条记录并返回成功或 9704;并发达到 50 条边界不得写入第 51 条活动项。 |
||||
|
- 首次进度记录若携带非 0 version,必须返回 9705。 |
||||
@ -0,0 +1,47 @@ |
|||||
|
# T7 任务简报:Onboarding 端到端与缺口测试 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W3 |
||||
|
- Depends on: T6 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
补齐模块级端到端和缺口测试,验证 9 个接口在真实 Spring 上下文、H2 MODE=MySQL 持久化、安全过滤和响应契约下协同工作。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingEndToEndIntegrationTest.java` |
||||
|
- 可在 `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/` 内补充测试缺口。 |
||||
|
- 只为修复测试暴露的 onboarding 缺陷修改 `com.mydreamjob.onboarding`、`ErrorCode.java` 或 onboarding 安全匹配。 |
||||
|
|
||||
|
## Required Coverage |
||||
|
|
||||
|
- 9 个路径均至少有成功或权限错误契约测试。 |
||||
|
- STUDENT:通用回退、企业合并、完成/取消、重复状态、版本冲突。 |
||||
|
- COMPANY:查询、创建、幂等重放、幂等冲突、上限、更新、软删除、重复删除、跨企业隔离。 |
||||
|
- STUDENT 公司知识:BASIC_ONLY、CONFIGURED、EMPTY、INSUFFICIENT、SUFFICIENT。 |
||||
|
- 评价过滤排除 HIDDEN 或非公开审核状态。 |
||||
|
- 匿名 401、错误角色 403、企业不存在 404。 |
||||
|
- 响应 JSON 不含 ownerUserId、studentUserId、moderationReason、hash、ip、userAgent、token。 |
||||
|
- 数据库行数和状态在所有错误路径后保持正确。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先写端到端/缺口测试并确认 RED 或明确复现缺口。 |
||||
|
2. 修复最小实现使测试 GREEN。 |
||||
|
3. 不扩大用户可见行为。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=Onboarding*Test test |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- 所有 onboarding 测试通过。 |
||||
|
- 没有仅覆盖 happy path 而遗漏契约错误路径。 |
||||
|
- 测试在随机顺序下仍稳定,不依赖真实时钟精度或外部服务。 |
||||
@ -0,0 +1,35 @@ |
|||||
|
# T8 聚焦修复 1:OpenAPI 示例与默认值 |
||||
|
|
||||
|
## Prior Failure |
||||
|
|
||||
|
`W3-T8.md` 发现: |
||||
|
|
||||
|
- `PUT /onboarding/company/knowledge` 的 200 示例缺少运行时响应必备的 `version`。 |
||||
|
- `CompanyChecklistItemCreateRequest` 缺少运行时默认值 `sortOrder=1000`、`required=false`。 |
||||
|
- `docs/verify_openapi.py` 未断言示例字段和 schema 默认值,因此未捕获上述偏差。 |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
修正 OpenAPI 契约,并增强验证脚本,使示例、默认值与运行时 DTO 一致。 |
||||
|
|
||||
|
## Required Changes |
||||
|
|
||||
|
- 在 `docs/openapi-rc.json` 中修正 `PUT /onboarding/company/knowledge` 的 200 示例,包含 `version`。 |
||||
|
- 为 `CompanyChecklistItemCreateRequest.sortOrder` 增加默认值 `1000`。 |
||||
|
- 为 `CompanyChecklistItemCreateRequest.required` 增加默认值 `false`。 |
||||
|
- 在 `docs/verify_openapi.py` 中增加精确断言: |
||||
|
- 知识 Upsert 200 示例的 `data.version` 存在。 |
||||
|
- 创建请求 schema 的两个默认值正确。 |
||||
|
- 不修改 runtime API 行为。 |
||||
|
|
||||
|
## Evidence |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai |
||||
|
python .\docs\verify_openapi.py |
||||
|
``` |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai\mydreamjob-server |
||||
|
mvn -Dtest=Onboarding*Test,CompanyKnowledge*Test test |
||||
|
``` |
||||
@ -0,0 +1,48 @@ |
|||||
|
# T8 任务简报:OpenAPI、接口文档与前端联调 |
||||
|
|
||||
|
## Where This Fits |
||||
|
|
||||
|
- Wave: W3 |
||||
|
- Depends on: T7 |
||||
|
- Contract: `changes/n04-onboard/execution-contract.md` |
||||
|
|
||||
|
## Objective |
||||
|
|
||||
|
发布 onboarding 的静态 OpenAPI、接口说明、前端联调说明和路线图状态,先用契约校验暴露文档缺口,再补齐文档。 |
||||
|
|
||||
|
## Files In Scope |
||||
|
|
||||
|
- `docs/openapi-rc.json` |
||||
|
- `docs/onboarding-api.md` |
||||
|
- `docs/frontend-integration.md` |
||||
|
- `docs/backend-development-roadmap.md` |
||||
|
- `README.md` |
||||
|
|
||||
|
## Required Behavior |
||||
|
|
||||
|
- OpenAPI 包含 9 个 `/onboarding` 路径和全部请求/响应 schema,operationId 全局唯一。 |
||||
|
- 文档说明角色、企业所有权、通用/企业项合并、完成状态版本、幂等键、软删除、知识回退、评价小样本阈值和 9701-9706。 |
||||
|
- 前端联调文档给出 Base URL、鉴权头、请求示例、响应示例、409/404/401/403 处理。 |
||||
|
- README 和 roadmap 将入职清单与公司知识介绍状态从“未开始”更新为“实现完成/待验收”。 |
||||
|
- 文档字段名、默认值、错误码与运行时 DTO 完全一致。 |
||||
|
|
||||
|
## TDD Requirement |
||||
|
|
||||
|
1. 先运行 `python docs\verify_openapi.py`,记录缺失路径或 schema 的失败证据。 |
||||
|
2. 再更新 OpenAPI 和文档,直到校验通过。 |
||||
|
3. 文档校验失败不得通过删除既有路径或放宽校验绕过。 |
||||
|
|
||||
|
## Evidence Commands |
||||
|
|
||||
|
```powershell |
||||
|
cd D:\1\zhi\zhilai |
||||
|
python .\docs\verify_openapi.py |
||||
|
cd .\mydreamjob-server |
||||
|
mvn clean verify |
||||
|
``` |
||||
|
|
||||
|
## Done Definition |
||||
|
|
||||
|
- OpenAPI 校验通过,9 个新路径可被 Apifox/前端消费。 |
||||
|
- 文档与 runtime 注解一致,无未说明错误。 |
||||
|
- 全量测试和打包通过。 |
||||
@ -0,0 +1,51 @@ |
|||||
|
# N04 Decision Point Audit |
||||
|
|
||||
|
审计时间:2026-09-17T08:03:00Z |
||||
|
|
||||
|
## Decision Point Status |
||||
|
|
||||
|
| DP | Status | Evidence | |
||||
|
| --- | --- | --- | |
||||
|
| DP-0 | confirmed | Full workflow、范围、非目标、语言和最小可用闭环已确认 | |
||||
|
| DP-1 | confirmed | 学生清单、企业配置、公司知识、评价摘要边界已确认 | |
||||
|
| DP-2 | approved | proposal、两份 delta specs、design、tasks 已审阅 | |
||||
|
| DP-3 | approved | execution contract B1-B3 与 12 条 Requirement 映射已批准 | |
||||
|
| DP-4 | approved | SDD;W1/W2/W3 串行 wave 和依赖已固化 | |
||||
|
| DP-5 | not-triggered | T6/T8 各一次聚焦修复后复评通过,未达到三轮升级条件 | |
||||
|
| DP-6 | pass with formal waiver | N04 专项、OpenAPI、打包通过;既有非 N04 全量失败由用户正式豁免;真实 MySQL/Docker 保留为发布前门禁 | |
||||
|
| DP-7 | confirmed | 用户授权归档并手工合并 feat/n04-onboard 到 master | |
||||
|
|
||||
|
## Contract Coverage |
||||
|
|
||||
|
- W1:V38/V39 三张表、通用清单种子、实体、Repository 和公开评价聚合。 |
||||
|
- W2:学生有效清单与完成状态、企业清单 CRUD、公司知识配置与聚合,66 个 wave 测试通过。 |
||||
|
- W3:权限、所有权、幂等、并发、端到端、隐私、OpenAPI 和文档,96 个 onboarding 回归通过。 |
||||
|
- 12 条 MUST/SHALL 行为均有实现、测试和批次证据。 |
||||
|
- 两份 delta specs 已发布到根 specs/,共 12 条 Requirements,无冲突。 |
||||
|
|
||||
|
## Verification Evidence |
||||
|
|
||||
|
- N04 专项:mvn -Dtest=Onboarding*Test,CompanyKnowledge*Test test,96 tests,0 failures,0 errors,BUILD SUCCESS。 |
||||
|
- 并发边界:REPEATABLE_READ 等价测试通过,49→50 时最多一条成功。 |
||||
|
- 随机顺序 E2E:7 tests,0 failures,0 errors,BUILD SUCCESS。 |
||||
|
- OpenAPI:135 paths / 172 operations;N04 6 paths / 9 operations;python docs\verify_openapi.py 全部通过。 |
||||
|
- 打包:mvn -DskipTests package,BUILD SUCCESS。 |
||||
|
- 全量:mvn clean verify,1492 tests,2 failures,9 errors,BUILD FAILURE。 |
||||
|
|
||||
|
## External Blockers |
||||
|
|
||||
|
- CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries 既有失败。 |
||||
|
- JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores 既有失败。 |
||||
|
- JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1 既有错误。 |
||||
|
- LocalResumeStorageServiceTest 8 个 JUnit 临时目录清理错误,属于既有环境问题。 |
||||
|
- 上述失败路径不在 N04 diff 中;需要在独立任务处理或由用户正式豁免。 |
||||
|
- 真实 MySQL 8.4 / Docker 黑盒验收尚未执行,当前证据为 H2 MODE=MySQL。 |
||||
|
|
||||
|
## Scope and Coherence |
||||
|
|
||||
|
- N04 变更集中在 com.mydreamjob.onboarding、V38/V39、SecurityConfig、ErrorCode、GlobalExceptionHandler、OpenAPI、接口文档和 onboarding 测试。 |
||||
|
- 未修改 V1-V38 历史迁移,未改变既有企业、岗位、Offer 或评价接口语义。 |
||||
|
- 所有 W3 任务和 wave 复审均 PASS;全量测试外部失败已单独记录。 |
||||
|
## Formal Waiver |
||||
|
|
||||
|
用户于 2026-09-17T02:47:50Z 明确批准:将与 N04 无关的既有全量测试失败作为正式豁免,并将真实 MySQL 8.4 / Docker 黑盒验收保留为发布前门禁。 |
||||
@ -0,0 +1,329 @@ |
|||||
|
# n04-onboard 设计 |
||||
|
|
||||
|
## Facts and Constraints |
||||
|
|
||||
|
- 当前后端为 Java 17、Spring Boot 3.5.16、Spring MVC、Spring Security、Spring Data JPA、Flyway、MySQL 8.4,测试使用 JUnit、Mockito、MockMvc 和 H2 `MODE=MySQL`。 |
||||
|
- 现有迁移已到 V37。本模块使用 V38 建表、V39 写入通用清单种子,不修改既有迁移或既有表结构。 |
||||
|
- `companies.owner_user_id` 唯一,现有 `CompanyService` 已提供 COMPANY 角色与所有权校验模式。 |
||||
|
- `job_reviews` 关联 `job_id`,企业评价必须通过 `jobs.company_id` 关联;公开评价限定为 `status=PUBLISHED` 且 `moderation_status IN (AUTO_APPROVED, APPROVED)`。 |
||||
|
- 现有八维评价维度由 `ReviewDimension` 枚举统一维护;公司知识摘要复用该枚举,不新增维度定义。 |
||||
|
- 统一响应使用 `ApiResponse`,错误使用 `BusinessException` 与 `ErrorCode`,请求 ID 从 `X-Request-Id` 读取。 |
||||
|
- 产品要求企业未配置时回退通用清单,公司信息不足时回退企业基础信息,且不调用 AI。 |
||||
|
|
||||
|
## Goals |
||||
|
|
||||
|
- 用最少的数据表和接口完成学生清单、个人完成状态、企业清单配置、公司知识配置与公开评价摘要。 |
||||
|
- 保持角色、企业所有权、响应字段和错误码可测试。 |
||||
|
- 保持创建幂等、更新并发可控、软删除可重复执行。 |
||||
|
- 不改变企业、岗位、Offer、评价等既有接口语义。 |
||||
|
|
||||
|
## Non-Goals |
||||
|
|
||||
|
- 不实现 ADMIN 模板治理、审核发布、版本回滚和 CMS。 |
||||
|
- 不实现通知、内容推荐、试用期提醒、入职回访、在职反馈。 |
||||
|
- 不按岗位、投递或 Offer 生成个性化内容。 |
||||
|
- 不存储或渲染 HTML/富文本,不接入 AI 生成或 AI 审核。 |
||||
|
- 不实现前端页面和前端缓存策略。 |
||||
|
|
||||
|
## Architecture |
||||
|
|
||||
|
新增包 `com.mydreamjob.onboarding`: |
||||
|
|
||||
|
```text |
||||
|
onboarding/ |
||||
|
├─ OnboardingChecklistController.java |
||||
|
├─ CompanyKnowledgeController.java |
||||
|
├─ OnboardingChecklistService.java |
||||
|
├─ CompanyKnowledgeService.java |
||||
|
├─ OnboardingChecklistItem.java |
||||
|
├─ StudentOnboardingProgress.java |
||||
|
├─ CompanyKnowledge.java |
||||
|
├─ OnboardingChecklistItemRepository.java |
||||
|
├─ StudentOnboardingProgressRepository.java |
||||
|
├─ CompanyKnowledgeRepository.java |
||||
|
└─ dto/ |
||||
|
├─ ChecklistItemView.java |
||||
|
├─ EffectiveChecklistResponse.java |
||||
|
├─ ProgressUpdateRequest.java |
||||
|
├─ ProgressUpdateResponse.java |
||||
|
├─ CompanyChecklistItemCreateRequest.java |
||||
|
├─ CompanyChecklistItemUpdateRequest.java |
||||
|
├─ CompanyChecklistItemView.java |
||||
|
├─ CompanyKnowledgeUpdateRequest.java |
||||
|
├─ CompanyKnowledgeView.java |
||||
|
├─ CompanyKnowledgeOverviewResponse.java |
||||
|
└─ ReviewSummaryView.java |
||||
|
``` |
||||
|
|
||||
|
`SecurityConfig` 将 `/onboarding/**` 设为认证访问;Controller/Service 再按角色和所有权执行细粒度校验。所有接口返回 `ApiResponse<T>`。 |
||||
|
|
||||
|
## API Contract |
||||
|
|
||||
|
所有路径均位于 `/api/v1` 之下。 |
||||
|
|
||||
|
| Method | Path | Role | Request | Success Response | |
||||
|
|---|---|---|---|---| |
||||
|
| GET | `/onboarding/companies/{companyId}/checklist` | STUDENT | path `companyId` | `EffectiveChecklistResponse` | |
||||
|
| PUT | `/onboarding/companies/{companyId}/checklist/items/{itemId}/completion` | STUDENT | `ProgressUpdateRequest` | `ProgressUpdateResponse` | |
||||
|
| GET | `/onboarding/companies/{companyId}/knowledge` | STUDENT | path `companyId` | `CompanyKnowledgeOverviewResponse` | |
||||
|
| GET | `/onboarding/company/checklist-items` | COMPANY | 无 | `List<CompanyChecklistItemView>` | |
||||
|
| POST | `/onboarding/company/checklist-items` | COMPANY | `CompanyChecklistItemCreateRequest` | `CompanyChecklistItemView` | |
||||
|
| PUT | `/onboarding/company/checklist-items/{itemId}` | COMPANY | `CompanyChecklistItemUpdateRequest` | `CompanyChecklistItemView` | |
||||
|
| DELETE | `/onboarding/company/checklist-items/{itemId}?version={version}` | COMPANY | query `version` | `CompanyChecklistItemView`,`active=false` | |
||||
|
| GET | `/onboarding/company/knowledge` | COMPANY | 无 | `CompanyKnowledgeView` | |
||||
|
| PUT | `/onboarding/company/knowledge` | COMPANY | `CompanyKnowledgeUpdateRequest` | `CompanyKnowledgeView` | |
||||
|
|
||||
|
### Effective Checklist Response |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"companyId": 2, |
||||
|
"companyName": "本源科技", |
||||
|
"customized": true, |
||||
|
"totalItems": 13, |
||||
|
"completedItems": 4, |
||||
|
"completionRate": 30.8, |
||||
|
"items": [ |
||||
|
{ |
||||
|
"itemId": 11, |
||||
|
"source": "COMPANY", |
||||
|
"itemKey": "company-2-uuid", |
||||
|
"title": "提交导师联系方式", |
||||
|
"description": "入职前由 HR 补充", |
||||
|
"category": "ADMIN", |
||||
|
"required": false, |
||||
|
"sortOrder": 200, |
||||
|
"active": true, |
||||
|
"completed": false, |
||||
|
"completedAt": null, |
||||
|
"version": 0 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
通用项使用 `source=STANDARD`;企业项使用 `source=COMPANY`。完成率保留一位小数,最大为 100.0;清单为空时完成率为 0.0。 |
||||
|
|
||||
|
### Completion Request |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"completed": true, |
||||
|
"version": 0 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- 无完成记录时 `version` 可省略或为 0。 |
||||
|
- 已有记录且请求值不同,必须提供当前 `version`;版本不一致返回 409 `ONBOARDING_PROGRESS_CONCURRENT_UPDATE`。 |
||||
|
- 已有记录且请求值与当前值相同,按幂等成功返回,不因旧版本报错。 |
||||
|
|
||||
|
### Company Item Create Request |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"title": "提交导师联系方式", |
||||
|
"description": "入职前由 HR 补充", |
||||
|
"category": "ADMIN", |
||||
|
"sortOrder": 200, |
||||
|
"required": false, |
||||
|
"idempotencyKey": "client-generated-key" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- `title` 必填,长度 1-120。 |
||||
|
- `description` 可空,最大 1000。 |
||||
|
- `category` 仅允许 `DOCUMENT`、`ADMIN`、`EQUIPMENT`、`TRAINING`、`LOGISTICS`、`OTHER`。 |
||||
|
- `sortOrder` 范围 0-9999,默认 1000。 |
||||
|
- `required` 默认 `false`。 |
||||
|
- `idempotencyKey` 可空,最大 64;服务端保存规范化请求体 SHA-256,用于同 key 不同内容冲突检测。 |
||||
|
|
||||
|
### Company Item Update/Delete |
||||
|
|
||||
|
更新请求包含 `version`;删除请求通过 `version` 查询参数传入。两者版本不匹配返回 409 `ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE`。删除采用软删除,重复删除按成功返回。 |
||||
|
|
||||
|
### Company Knowledge Request |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"culture": "重视透明沟通和学习", |
||||
|
"workPolicies": "弹性工作制;每周三远程", |
||||
|
"onboardingGuide": "9:30 到 A 座 8 层前台报到", |
||||
|
"version": 0 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- 三个文本字段可空,但至少一个非空。 |
||||
|
- `culture` 最大 2000,`workPolicies` 最大 4000,`onboardingGuide` 最大 4000。 |
||||
|
- 首次创建不要求版本;已有记录必须提供当前版本,冲突返回 409 `ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE`。 |
||||
|
|
||||
|
### Student Company Knowledge Response |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"company": { |
||||
|
"id": 2, |
||||
|
"name": "本源科技", |
||||
|
"logoUrl": null, |
||||
|
"description": "企业简介", |
||||
|
"industry": "互联网", |
||||
|
"website": "https://example.com", |
||||
|
"address": "上海市", |
||||
|
"verified": true |
||||
|
}, |
||||
|
"knowledgeState": "CONFIGURED", |
||||
|
"knowledge": { |
||||
|
"culture": "重视透明沟通和学习", |
||||
|
"workPolicies": "弹性工作制;每周三远程", |
||||
|
"onboardingGuide": "9:30 到 A 座 8 层前台报到", |
||||
|
"updatedAt": "2026-09-16T07:00:00" |
||||
|
}, |
||||
|
"reviewSummary": { |
||||
|
"state": "SUFFICIENT", |
||||
|
"totalReviews": 12, |
||||
|
"averageRating": 4.4, |
||||
|
"ratingDistribution": { |
||||
|
"rating1": 0, |
||||
|
"rating2": 1, |
||||
|
"rating3": 2, |
||||
|
"rating4": 4, |
||||
|
"rating5": 5 |
||||
|
}, |
||||
|
"topTags": ["成长空间", "团队氛围"], |
||||
|
"dimensionSummary": { |
||||
|
"TEAM_ATMOSPHERE": { |
||||
|
"averageScore": 4.5, |
||||
|
"sampleCount": 8, |
||||
|
"dataState": "SUFFICIENT" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
无知识记录时 `knowledgeState=BASIC_ONLY`、`knowledge=null`,企业基础信息仍返回。评价少于 3 条时 `reviewSummary.state=INSUFFICIENT` 或 `EMPTY`,且 `averageRating`、`ratingDistribution`、`topTags`、`dimensionSummary` 为空。 |
||||
|
|
||||
|
## Data Model |
||||
|
|
||||
|
### `onboarding_checklist_items` |
||||
|
|
||||
|
```text |
||||
|
id BIGINT PK |
||||
|
scope_type VARCHAR(20) NOT NULL -- STANDARD / COMPANY |
||||
|
scope_id BIGINT NOT NULL -- 0 或 company_id |
||||
|
company_id BIGINT NULL -- 企业项外键;通用项为 NULL |
||||
|
item_key VARCHAR(80) NOT NULL |
||||
|
title VARCHAR(120) NOT NULL |
||||
|
description VARCHAR(1000) NULL |
||||
|
category VARCHAR(30) NOT NULL |
||||
|
sort_order INT NOT NULL DEFAULT 1000 |
||||
|
required BOOLEAN NOT NULL DEFAULT FALSE |
||||
|
active BOOLEAN NOT NULL DEFAULT TRUE |
||||
|
idempotency_key VARCHAR(64) NULL |
||||
|
request_hash CHAR(64) NULL |
||||
|
version BIGINT NOT NULL DEFAULT 0 |
||||
|
created_at DATETIME NOT NULL |
||||
|
updated_at DATETIME NOT NULL |
||||
|
UNIQUE (scope_type, scope_id, item_key) |
||||
|
UNIQUE (company_id, idempotency_key) |
||||
|
INDEX (scope_type, scope_id, active, sort_order, id) |
||||
|
``` |
||||
|
|
||||
|
`scope_type/scope_id` 为公司项唯一性提供稳定语义,同时允许通用项使用 `scope_id=0`;`company_id` 仅对企业项建立外键,标准项为 NULL。应用层检查 `scope_type=COMPANY` 时 `scope_id=company_id` 且 `company_id` 非空。 |
||||
|
|
||||
|
### `student_onboarding_progress` |
||||
|
|
||||
|
```text |
||||
|
id BIGINT PK |
||||
|
student_user_id BIGINT NOT NULL |
||||
|
item_id BIGINT NOT NULL |
||||
|
completed BOOLEAN NOT NULL |
||||
|
completed_at DATETIME NULL |
||||
|
version BIGINT NOT NULL DEFAULT 0 |
||||
|
created_at DATETIME NOT NULL |
||||
|
updated_at DATETIME NOT NULL |
||||
|
UNIQUE (student_user_id, item_id) |
||||
|
FOREIGN KEY student_user_id -> users(id) |
||||
|
FOREIGN KEY item_id -> onboarding_checklist_items(id) |
||||
|
``` |
||||
|
|
||||
|
完成状态采用绝对值 Upsert;同一学生与条目唯一。`completedAt` 在首次置为完成时写入,取消完成时清空。 |
||||
|
|
||||
|
### `company_knowledge` |
||||
|
|
||||
|
```text |
||||
|
id BIGINT PK |
||||
|
company_id BIGINT NOT NULL |
||||
|
culture VARCHAR(2000) NULL |
||||
|
work_policies VARCHAR(4000) NULL |
||||
|
onboarding_guide VARCHAR(4000) NULL |
||||
|
version BIGINT NOT NULL DEFAULT 0 |
||||
|
created_at DATETIME NOT NULL |
||||
|
updated_at DATETIME NOT NULL |
||||
|
UNIQUE (company_id) |
||||
|
FOREIGN KEY company_id -> companies(id) |
||||
|
``` |
||||
|
|
||||
|
## Decisions |
||||
|
|
||||
|
### Decision 1: 使用统一清单表并保留标准/企业来源 |
||||
|
|
||||
|
- **Choice**:`onboarding_checklist_items` 使用 `scope_type` 区分 `STANDARD` 与 `COMPANY`;有效清单查询返回两类记录。 |
||||
|
- **Rationale**:学生只需要一个稳定 itemId 更新完成状态,统一表避免两套进度表、两套排序和重复响应逻辑。 |
||||
|
- **Alternatives**:标准表与企业表分离;企业配置 JSON 存储在一个字段中。 |
||||
|
- **Consequences**:需要额外维护 scope 一致性检查;换来了统一的完成状态和软删除语义。 |
||||
|
|
||||
|
### Decision 2: 通用清单固定种子,企业项只做增量 |
||||
|
|
||||
|
- **Choice**:V39 写入通用清单;企业自定义项只追加,不允许覆盖或替换通用项。 |
||||
|
- **Rationale**:产品明确要求企业未配置时显示通用清单,固定基线可保证所有企业至少有可用内容。 |
||||
|
- **Alternatives**:允许企业禁用通用项;允许企业完全替换模板。 |
||||
|
- **Consequences**:MVP 不支持企业裁剪通用项;后续若需要,需新增停用映射而不是修改现有响应语义。 |
||||
|
|
||||
|
### Decision 3: 创建幂等与更新乐观锁分离 |
||||
|
|
||||
|
- **Choice**:创建使用 `idempotencyKey + request_hash`;更新、删除和知识内容使用 `@Version` 乐观锁。 |
||||
|
- **Rationale**:创建需要处理网络重试,更新需要避免管理员或企业多端并发覆盖。 |
||||
|
- **Alternatives**:所有写接口只依赖数据库唯一键;所有接口都使用请求幂等键。 |
||||
|
- **Consequences**:客户端创建时需保存幂等键;更新时必须先取得当前版本。 |
||||
|
|
||||
|
### Decision 4: 公司知识摘要只使用公开评价并执行小样本保护 |
||||
|
|
||||
|
- **Choice**:只聚合 `PUBLISHED` 且审核为 `AUTO_APPROVED/APPROVED` 的评价;少于 3 条不返回均值。 |
||||
|
- **Rationale**:与现有公开评价语义一致,降低小样本隐私和误导风险。 |
||||
|
- **Alternatives**:聚合全部评价;无阈值返回所有均值。 |
||||
|
- **Consequences**:新企业或评价较少企业只能看到基础资料与评价数量,不显示汇总分数。 |
||||
|
|
||||
|
### Decision 5: 学生访问不绑定 Offer |
||||
|
|
||||
|
- **Choice**:任何 STUDENT 可按公开存在的 `companyId` 读取清单和知识,完成状态只属于当前学生。 |
||||
|
- **Rationale**:产品没有要求入职资格校验,企业基础信息和通用清单也不属于敏感业务数据;此设计避免跨模块状态耦合。 |
||||
|
- **Alternatives**:必须存在 ACCEPTED Offer;必须存在投递记录。 |
||||
|
- **Consequences**:后续若要增加资格门槛,需新增权限规则和兼容测试,不改变现有路径。 |
||||
|
|
||||
|
## Error Codes |
||||
|
|
||||
|
新增 97xx: |
||||
|
|
||||
|
```text |
||||
|
9701 ONBOARDING_CHECKLIST_ITEM_NOT_FOUND |
||||
|
9702 ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE |
||||
|
9703 ONBOARDING_ITEM_LIMIT_EXCEEDED |
||||
|
9704 ONBOARDING_IDEMPOTENCY_CONFLICT |
||||
|
9705 ONBOARDING_PROGRESS_CONCURRENT_UPDATE |
||||
|
9706 ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE |
||||
|
``` |
||||
|
|
||||
|
复用 `FORBIDDEN`、`UNAUTHORIZED`、`BAD_REQUEST`、`COMPANY_NOT_FOUND`。 |
||||
|
|
||||
|
## Risks and Verification |
||||
|
|
||||
|
| Risk | Verification | |
||||
|
|---|---| |
||||
|
| V38/V39 与 MySQL/H2 类型不一致 | 迁移集成测试检查三张表、唯一键、外键、种子数量与排序 | |
||||
|
| 企业跨租户访问条目 | Service/MockMvc 测试使用两个企业,断言跨企业返回 404 且数据未变化 | |
||||
|
| 重复创建产生重复项 | 持久化测试断言相同 idempotencyKey 只有一行;不同请求体断言 9704 | |
||||
|
| 并发更新静默覆盖 | 持久化测试和乐观锁冲突测试断言 9702/9706 | |
||||
|
| 完成状态重复或丢失 | 同一学生并发/重复 PUT 测试断言唯一记录、完成率和 completedAt 正确 | |
||||
|
| 评价聚合暴露小样本或内部字段 | DTO 白名单测试、少于 3 条测试、无知识回退测试 | |
||||
|
| 公司知识内容注入 HTML | 请求字段按纯文本存储和返回;长度与内容验证测试 | |
||||
|
| 既有模块回归 | 扩展评价/企业相关测试后执行 `mvn clean verify`,并与 baseline 对比路径数 | |
||||
@ -0,0 +1,165 @@ |
|||||
|
# N04 入职清单与公司知识介绍执行契约 |
||||
|
|
||||
|
## Intent Lock |
||||
|
|
||||
|
**问题**:产品一期要求提供入职清单和公司知识介绍,但当前后端没有对应表、接口、权限或测试。学生无法获得“通用准备项 + 企业自定义项”的有效清单,无法保存个人完成状态,也无法集中查看企业基础资料、文化制度、入职指南和公开评价摘要;企业无法维护本公司差异内容。 |
||||
|
|
||||
|
**意图**:新增最小可用的 `onboarding` 后端模块。通过 V38/V39 建立三张业务表和通用清单种子;提供 9 个确定性接口;STUDENT 读取有效清单、更新本人完成状态、读取公司知识,COMPANY 仅维护本企业自定义清单与知识内容;公司知识使用企业基础资料、企业维护内容及公开评价摘要,其中公开评价少于 3 条时不返回均值。不调用 AI,不引入 ADMIN 内容治理。 |
||||
|
|
||||
|
**完成证明**:V38/V39 在 H2 与真实 MySQL 成功执行;9 个接口的权限、所有权、幂等、并发、回退、排序、完成率、评价摘要和小样本保护均通过自动化测试;OpenAPI、接口文档和前端联调文档与运行时一致;`mvn clean verify` 与 `python docs/verify_openapi.py` 通过。 |
||||
|
|
||||
|
## Scope Fence |
||||
|
|
||||
|
### 允许范围 |
||||
|
|
||||
|
- 新增 `mydreamjob-server/src/main/resources/db/migration/V38__onboarding_checklist_and_company_knowledge.sql` 和 `V39__onboarding_standard_checklist_seed.sql`。 |
||||
|
- 新增 `com.mydreamjob.onboarding` 领域包,包括实体、Repository、Service、Controller、DTO 和测试。 |
||||
|
- 修改 `SecurityConfig.java`,使 `/onboarding/**` 仅允许认证访问,并在服务层执行 STUDENT/COMPANY 角色与公司所有权校验。 |
||||
|
- 修改 `ErrorCode.java`,新增 9701-9706。 |
||||
|
- 更新 `docs/openapi-rc.json`、`docs/onboarding-api.md`、`docs/frontend-integration.md`、`docs/backend-development-roadmap.md` 和 `README.md`。 |
||||
|
- 只读复用 `Company`、`CompanyRepository`、`Job`、`JobReview`、`JobReviewDimensionScore` 及其 Repository。 |
||||
|
- 采用 9 个固定接口: |
||||
|
- `GET /api/v1/onboarding/companies/{companyId}/checklist` |
||||
|
- `PUT /api/v1/onboarding/companies/{companyId}/checklist/items/{itemId}/completion` |
||||
|
- `GET /api/v1/onboarding/companies/{companyId}/knowledge` |
||||
|
- `GET /api/v1/onboarding/company/checklist-items` |
||||
|
- `POST /api/v1/onboarding/company/checklist-items` |
||||
|
- `PUT /api/v1/onboarding/company/checklist-items/{itemId}` |
||||
|
- `DELETE /api/v1/onboarding/company/checklist-items/{itemId}?version={version}` |
||||
|
- `GET /api/v1/onboarding/company/knowledge` |
||||
|
- `PUT /api/v1/onboarding/company/knowledge` |
||||
|
|
||||
|
### 禁止范围 |
||||
|
|
||||
|
- 不修改 V1-V37 历史迁移或既有表结构。 |
||||
|
- 不修改既有企业、岗位、Offer、评价和维度评分接口的路径、请求、响应或权限语义。 |
||||
|
- 不实现 ADMIN 模板管理、审核发布、版本回滚、CMS 或内容治理后台。 |
||||
|
- 不实现通知、内容推荐、试用期提醒、入职回访、在职反馈或前端页面。 |
||||
|
- 不按岗位、投递或 Offer 生成个性化清单,不引入入职资格门槛。 |
||||
|
- 不接 AI、第三方内容平台、外部搜索或实时同步。 |
||||
|
- 不存储或渲染 HTML/富文本;知识字段按纯文本处理并限制长度。 |
||||
|
- 不在响应或日志中输出 `ownerUserId`、员工用户 ID、手机号、邮箱、Token、IP、User-Agent、评价哈希或审核原因。 |
||||
|
- 不启动或停止 Docker;真实 MySQL/Docker 黑盒验收需要用户单独授权。 |
||||
|
|
||||
|
## Approved Behavior |
||||
|
|
||||
|
| ID | 已批准行为 | 批次 | |
||||
|
| --- | --- | --- | |
||||
|
| REQ-OC-1 | STUDENT 按存在的 `companyId` 获取有效清单;响应包含通用项与企业活动自定义项、排序、分类、必填标记、个人完成状态、完成数、总数、完成率和 `customized`;企业不存在返回 404。 | B1、B2 | |
||||
|
| REQ-OC-2 | STUDENT 通过 PUT 将本人对有效条目的状态设置为完成或未完成;相同状态重复提交幂等;取消完成清除 `completedAt`;跨企业或非活动 itemId 返回 404;响应返回版本和最新完成率。 | B1、B2、B3 | |
||||
|
| REQ-OC-3 | COMPANY 创建本企业自定义项;请求校验标题、说明、分类、排序和必填标记;相同 `idempotencyKey` 与相同请求体返回原条目,不同请求体返回 409 `ONBOARDING_IDEMPOTENCY_CONFLICT`;活动自定义项达到 50 条后返回 409 `ONBOARDING_ITEM_LIMIT_EXCEEDED`。 | B1、B2、B3 | |
||||
|
| REQ-OC-4 | COMPANY 查询本企业全部自定义项;使用当前 `version` 更新标题、说明、分类、排序、必填和活动状态;过期版本返回 409 `ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE`;不得读取或修改其他企业条目。 | B1、B2、B3 | |
||||
|
| REQ-OC-5 | COMPANY 使用当前 `version` 软删除本企业条目;条目从学生有效清单消失,历史和完成记录保留;重复删除幂等成功;跨企业删除返回 404 且状态不变。 | B1、B2、B3 | |
||||
|
| REQ-OC-6 | 学生接口仅允许 STUDENT;企业配置接口仅允许 COMPANY 且只能操作当前用户拥有的企业;未登录返回 401,角色不符返回 403,无企业资料返回 404;所有响应使用白名单 DTO。 | B2、B3 | |
||||
|
| REQ-CK-1 | STUDENT 按存在的 `companyId` 读取公司知识;企业有知识记录返回 `CONFIGURED`,无记录返回 `BASIC_ONLY` 和企业基础资料;不得返回 `ownerUserId` 等内部字段;企业不存在返回 404。 | B1、B2 | |
||||
|
| REQ-CK-2 | 公司评价摘要只使用该公司岗位下 `PUBLISHED` 且审核为 `AUTO_APPROVED/APPROVED` 的评价;无评价返回 `EMPTY`;少于 3 条返回 `INSUFFICIENT` 和不含均值的真实数量;至少 3 条返回平均分、评分分布、热门标签和八维摘要,维度返回样本数和 `SUFFICIENT/INSUFFICIENT`。 | B1、B2、B3 | |
||||
|
| REQ-CK-3 | COMPANY 读取本企业知识配置;无记录返回 `configured=false`、空内容和版本 0;有记录返回内容、更新时间和当前版本。 | B1、B2 | |
||||
|
| REQ-CK-4 | COMPANY Upsert 本企业知识内容;文化最多 2000 字符,工作制度和入职指南最多 4000 字符,至少一个字段非空,按纯文本处理;已有记录使用当前版本,冲突返回 409 `ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE`。 | B1、B2、B3 | |
||||
|
| REQ-CK-5 | 公司知识学生读取仅允许 STUDENT;企业配置仅允许 COMPANY 且只操作本企业;非角色访问返回 401/403;不存在的公司返回 404。 | B2、B3 | |
||||
|
| REQ-CK-6 | 本变更只读复用既有企业、岗位、评价和维度数据,不修改历史表结构和既有接口语义;既有测试与 OpenAPI 路径必须回归通过。 | B3 | |
||||
|
|
||||
|
需求覆盖结果:两份 spec 中全部 12 条 Requirement 的 `MUST`/`SHALL` 行为均已映射到 Approved Behavior、至少一个测试义务和至少一个执行批次,无未映射需求。 |
||||
|
|
||||
|
## Architecture and Interface Constraints |
||||
|
|
||||
|
- 数据模型固定为 `onboarding_checklist_items`、`student_onboarding_progress`、`company_knowledge` 三张表。 |
||||
|
- `onboarding_checklist_items` 使用 `scope_type/scope_id/item_key` 区分并唯一化 STANDARD 与 COMPANY 条目;`company_id` 仅对企业项建立外键。 |
||||
|
- `student_onboarding_progress(student_user_id, item_id)` 唯一;完成状态采用绝对值 Upsert,`completedAt` 仅在完成时存在。 |
||||
|
- `company_knowledge.company_id` 唯一;知识内容按纯文本存储并使用 `@Version`。 |
||||
|
- 通用清单由 V39 固定种子提供,企业自定义项只增量追加,不允许覆盖或裁剪通用项。 |
||||
|
- 学生有效清单必须在一次查询中合并活动通用项和企业项,并使用 `sortOrder ASC, id ASC` 稳定排序。 |
||||
|
- 完成率计算为 `completedItems * 100.0 / totalItems`,保留一位小数并最大为 100.0;空清单为 0.0。 |
||||
|
- 创建清单项使用 `idempotencyKey` 和规范化请求体 SHA-256;更新、删除、完成状态和知识内容使用乐观锁语义。 |
||||
|
- 公司评价摘要必须通过公司下所有岗位聚合,不得只使用单个岗位汇总;热门标签从公开评价 tags 中计数,按次数降序、字典序升序,最多 10 个。 |
||||
|
- 八维摘要复用 `ReviewDimension`,八个维度均返回,缺失维度样本数为 0、`dataState=INSUFFICIENT`。 |
||||
|
- 所有响应使用 `ApiResponse<T>`;请求 ID 从 `X-Request-Id` 读取。 |
||||
|
- 新增错误码固定为: |
||||
|
- `9701 ONBOARDING_CHECKLIST_ITEM_NOT_FOUND` |
||||
|
- `9702 ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE` |
||||
|
- `9703 ONBOARDING_ITEM_LIMIT_EXCEEDED` |
||||
|
- `9704 ONBOARDING_IDEMPOTENCY_CONFLICT` |
||||
|
- `9705 ONBOARDING_PROGRESS_CONCURRENT_UPDATE` |
||||
|
- `9706 ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE` |
||||
|
- 复用 `FORBIDDEN`、`UNAUTHORIZED`、`BAD_REQUEST` 和 `COMPANY_NOT_FOUND`。 |
||||
|
- 迁移编号以当前仓库下一个可用版本为准;若 V38/V39 被其他变更占用,必须回退规划阶段刷新编号和契约。 |
||||
|
|
||||
|
## Execution Batches |
||||
|
|
||||
|
### Batch B1: 数据模型、迁移与只读聚合基础 |
||||
|
|
||||
|
- 覆盖任务:T1、T2。 |
||||
|
- 交付物:V38/V39 迁移、三个实体、三个模块 Repository、现有评价跨岗位聚合查询、迁移和 Repository 集成测试。 |
||||
|
- 完成定义:H2/MySQL 可创建三张表、约束和索引;通用种子数量和排序稳定;活动通用项与企业项可合并查询;学生完成记录可批量读取;公司公开评价可按评分、维度和 tags 聚合。 |
||||
|
- 依赖:无。 |
||||
|
- 评审门:B1 完成并由 `code-reviewer` 记录 `pass` 后才能开始 B2。 |
||||
|
|
||||
|
### Batch B2: 学生清单、企业配置与公司知识接口 |
||||
|
|
||||
|
- 覆盖任务:T3、T4、T5。 |
||||
|
- 交付物:两个 Controller、两个 Service、全部 DTO、清单合并与完成状态、自定义项 CRUD、知识 Upsert、基础信息和评价摘要组装、Service 与 MockMvc 专项测试。 |
||||
|
- 完成定义:REQ-OC-1 至 REQ-OC-6、REQ-CK-1 至 REQ-CK-5 的接口和服务行为实现;企业无自定义项时回退通用清单;无知识时返回基础信息;公开评价少于 3 条不返回均值。 |
||||
|
- 依赖:B1 通过评审。 |
||||
|
- 评审门:B2 完成并由 `code-reviewer` 记录 `pass` 后才能开始 B3。 |
||||
|
|
||||
|
### Batch B3: 权限、并发、契约、全量测试与文档 |
||||
|
|
||||
|
- 覆盖任务:T6、T7、T8。 |
||||
|
- 交付物:`SecurityConfig` 与 `ErrorCode` 更新、角色和所有权集成测试、幂等与乐观锁冲突测试、隐私断言、端到端测试、OpenAPI、接口文档和前端联调文档。 |
||||
|
- 完成定义:匿名 401、非角色 403、企业不存在 404、跨企业 404、版本冲突 409、幂等冲突 409、活动项上限 409 均符合契约;OpenAPI 包含 9 个路径并运行时一致;`mvn clean verify` 与 `python docs/verify_openapi.py` 通过。 |
||||
|
- 依赖:B2 通过评审。 |
||||
|
- 评审门:B3 完成并由 `code-reviewer` 记录 `pass` 后才能进入验证和收尾。 |
||||
|
|
||||
|
## Test Obligations |
||||
|
|
||||
|
| ID | 测试义务 | 批次 | 证据 | |
||||
|
| --- | --- | --- | --- | |
||||
|
| TO-1 | V38/V39 创建三张表、主外键、唯一键和索引;通用种子内容、分类和排序稳定。 | B1 | `mvn -Dtest=OnboardingMigrationTest,OnboardingSeedDataIntegrationTest test` | |
||||
|
| TO-2 | 企业无自定义项回退通用清单;有项时合并排序、来源和 category 正确。 | B1、B2 | Repository 集成测试、`OnboardingChecklistServiceTest` | |
||||
|
| TO-3 | 完成状态首次设置、重复相同状态、取消完成、跨企业 itemId 和完成率。 | B2 | `mvn -Dtest=OnboardingChecklistServiceTest,OnboardingChecklistControllerTest test` | |
||||
|
| TO-4 | 创建校验、幂等重放、请求体哈希冲突和 50 条活动项上限。 | B2、B3 | `OnboardingCompanyChecklistServiceTest`、持久化测试 | |
||||
|
| TO-5 | 企业列表、更新、软删除、版本冲突、重复删除和跨企业隔离。 | B2、B3 | `OnboardingCompanyChecklistServiceTest`、`OnboardingConcurrencyIntegrationTest` | |
||||
|
| TO-6 | STUDENT/COMPANY/ADMIN/匿名权限矩阵和白名单响应。 | B2、B3 | `OnboardingAuthorizationIntegrationTest`、`OnboardingPrivacyTest` | |
||||
|
| TO-7 | 公司知识无记录回退、企业读取、首次创建、更新和长度校验。 | B2 | `mvn -Dtest=CompanyKnowledgeServiceTest,CompanyKnowledgeControllerTest test` | |
||||
|
| TO-8 | 评价只取公开记录;0、1、2、3 条边界;评分分布、标签和八维摘要。 | B1、B2 | 聚合 Repository 测试、`CompanyKnowledgeServiceTest` | |
||||
|
| TO-9 | 知识版本冲突返回 9706,不覆盖新内容。 | B2、B3 | `OnboardingConcurrencyIntegrationTest` | |
||||
|
| TO-10 | 9 个接口的状态码、请求/响应 DTO、错误码和 OpenAPI 一致性。 | B3 | `OnboardingEndToEndIntegrationTest`、`python docs/verify_openapi.py` | |
||||
|
| TO-11 | 迁移、种子、实体、Repository、Service、Controller 和权限集成场景全部可重复。 | B1、B2、B3 | `mvn -Dtest=Onboarding*Test test` | |
||||
|
| TO-12 | 既有模块回归、完整打包、静态契约和无敏感字段泄露。 | B3 | `mvn clean verify`、`python docs/verify_openapi.py` | |
||||
|
|
||||
|
## Build Rules |
||||
|
|
||||
|
- 每个批次按 TDD 先写失败测试,再实现最小行为,不得跳过测试。 |
||||
|
- 只修改允许范围内文件;发现需要修改已验收接口、历史迁移或既有响应语义时立即停止并触发 Rewind。 |
||||
|
- 迁移和种子数据必须可在 H2(`MODE=MySQL`)与真实 MySQL 执行,不依赖外部文件路径。 |
||||
|
- 所有清单排序、完成率、评价汇总和标签排序必须确定性;禁止随机数、当前时间或请求顺序影响结果。 |
||||
|
- 新增日志不得包含个人完成明细、Token、手机号、邮箱、IP、User-Agent、评价哈希或审核原因。 |
||||
|
- OpenAPI 注解、示例和实际响应必须一致,不能只改文档或只改运行时。 |
||||
|
- 企业内容按纯文本处理,不拼接 HTML;前端展示安全由 JSON 字符串和前端转义共同保证。 |
||||
|
- 不启动或停止 Docker;若需要真实 MySQL/Docker 黑盒验证,必须先取得用户单独授权。 |
||||
|
|
||||
|
## Review Gates |
||||
|
|
||||
|
- **DP-3**:本执行契约必须由用户明确批准并记录 `dp_3_result`、`dp_3_timestamp` 后,才能开始任何实现。 |
||||
|
- **Batch Review**:B1、B2、B3 每批完成后由 `code-reviewer` 按规范符合性和代码质量评审,记录 `pass` 或 `fail`。 |
||||
|
- **DP-4**:批准契约后由 `build-executor` 给出执行模式和 wave 计划,用户确认后开始执行。 |
||||
|
- **Verification Gate**:实现完成后执行专项测试、全量测试、OpenAPI 校验和必要的 MySQL/Docker 黑盒验证。 |
||||
|
- **DP-7**:由 `release-archivist` 完成最终验证与关闭,不在本契约内自动完成。 |
||||
|
|
||||
|
## Rewind Conditions |
||||
|
|
||||
|
出现以下任一情况必须停止执行并回到规划阶段刷新工件和本契约: |
||||
|
|
||||
|
- 需要新增、删除或改变用户可见接口、请求字段、响应字段、权限或错误语义。 |
||||
|
- 需要改变数据表结构、唯一键、外键、通用/企业项来源模型或迁移编号。 |
||||
|
- 需要改变完成状态幂等、乐观锁、完成率公式或软删除语义。 |
||||
|
- 需要改变公开评价审核状态、3 条小样本阈值、八维维度或热门标签排序。 |
||||
|
- 需要引入 ADMIN 模板治理、内容审核、版本回滚、通知、推荐、Offer 资格门槛或 AI。 |
||||
|
- 需要修改 V1-V37、既有企业/岗位/评价接口或既有表结构。 |
||||
|
- V38/V39 被其他变更占用。 |
||||
|
- 发现任一已批准 `MUST`/`SHALL` 无法映射到测试义务或执行批次。 |
||||
|
|
||||
|
## DP-3 Approval Gate |
||||
|
|
||||
|
执行者确认:本契约完整覆盖已批准的 12 条 Requirement,无未映射需求;B1→B2→B3 依赖清晰;不同批次之间的实体、Repository、DTO 和错误码接口已在 Architecture and Interface Constraints 中固定;所有测试义务均有证据命令。 |
||||
|
|
||||
|
**批准方式**:用户明确回复批准本执行契约后,记录 `dp_3_result`、`dp_3_timestamp`,再执行 `specifying → bridging → approved-for-build` 状态转换。DP-3 记录前禁止进入 `build-executor`。 |
||||
@ -0,0 +1,65 @@ |
|||||
|
# n04-onboard 入职清单与公司知识介绍 |
||||
|
|
||||
|
## Why |
||||
|
|
||||
|
产品功能表将 M1.1「入职清单」和 M1.2「公司了解」列为一期 P2 功能,但当前后端没有对应表、服务、API、权限或测试。学生缺少可执行的标准入职准备事项,也无法在一个接口中集中查看企业基础资料、企业文化与制度、员工评价摘要;企业也没有维护自定义清单和公司知识内容的入口。 |
||||
|
|
||||
|
本变更以最小可用闭环补齐后端能力:通用清单提供稳定基线,企业自定义项补足差异,学生的个人完成状态可持续计算完成率;公司知识优先展示企业已维护内容与公开评价摘要,信息不足时合法回退到企业基础信息。 |
||||
|
|
||||
|
## What Changes |
||||
|
|
||||
|
- 新增 `onboarding` 后端模块,提供 9 个接口: |
||||
|
- `GET /onboarding/companies/{companyId}/checklist` |
||||
|
- `PUT /onboarding/companies/{companyId}/checklist/items/{itemId}/completion` |
||||
|
- `GET /onboarding/companies/{companyId}/knowledge` |
||||
|
- `GET /onboarding/company/checklist-items` |
||||
|
- `POST /onboarding/company/checklist-items` |
||||
|
- `PUT /onboarding/company/checklist-items/{itemId}` |
||||
|
- `DELETE /onboarding/company/checklist-items/{itemId}` |
||||
|
- `GET /onboarding/company/knowledge` |
||||
|
- `PUT /onboarding/company/knowledge` |
||||
|
- 新增三类持久化数据:通用/企业自定义清单项、学生对清单项的完成状态、企业知识内容。 |
||||
|
- 通过 Flyway 写入通用入职清单种子数据;企业未配置自定义项时,学生仍获得通用清单。 |
||||
|
- 公司知识聚合企业基础资料、企业维护的文化/制度/入职指南,以及该公司所有公开岗位评价的汇总;有效公开评价少于 3 条时不展示平均分,避免小样本暴露。 |
||||
|
- 企业配置接口仅允许企业维护本公司数据;学生接口仅允许学生读取公司资料并更新本人清单状态。 |
||||
|
- 补齐错误码、幂等键、乐观锁、MockMvc、Service、持久化、迁移、端到端测试、OpenAPI 与前端联调文档。 |
||||
|
|
||||
|
## Scope |
||||
|
|
||||
|
### In Scope |
||||
|
|
||||
|
- 通用清单的数据库种子数据、排序、分类和必填标记。 |
||||
|
- 企业自定义清单项的创建、查询、更新和软删除。 |
||||
|
- 学生按企业读取有效清单、查看个人完成状态、设置完成或未完成。 |
||||
|
- 企业知识内容的读取与 Upsert。 |
||||
|
- 公司知识介绍聚合公司基础资料与公开评价摘要,并支持 `BASIC_ONLY` 回退。 |
||||
|
- COMPANY 所有权校验、STUDENT 角色校验、敏感字段白名单响应。 |
||||
|
- 创建幂等、更新/删除乐观锁、并发冲突错误码和重复请求重放。 |
||||
|
- 模块自动化测试、OpenAPI 静态契约校验和前端联调文档。 |
||||
|
|
||||
|
### Out of Scope |
||||
|
|
||||
|
- ADMIN 管理通用模板、发布审核、版本回滚和内容治理后台。 |
||||
|
- 通知提醒、职场内容推荐、试用期提醒、入职回访和在职反馈。 |
||||
|
- 按岗位、投递或 Offer 生成个性化清单。 |
||||
|
- 第三方 CMS、富文本 HTML、AI 生成公司介绍或 AI 内容审核。 |
||||
|
- 学生端页面、企业端页面和前端状态管理。 |
||||
|
- 修改已有企业、岗位、Offer 或评价接口的响应语义。 |
||||
|
|
||||
|
## Impact |
||||
|
|
||||
|
- `mydreamjob-server/src/main/resources/db/migration/`:新增 V38、V39 迁移,不修改 V1-V37。 |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/`:新增领域实体、Repository、Service、Controller 和 DTO。 |
||||
|
- `mydreamjob-server/src/main/java/com/mydreamjob/common/exception/ErrorCode.java`:新增 97xx 错误码。 |
||||
|
- `docs/openapi-rc.json`、`docs/onboarding-api.md`、`docs/frontend-integration.md`、路线图和 README 状态说明。 |
||||
|
- 现有 `Company`、`Job`、`JobReview`、`JobReviewDimensionScore` 只读复用,不改变其表结构和接口。 |
||||
|
- 新增表使用 Flyway,支持 MySQL 8.4 与 H2 `MODE=MySQL` 集成测试。 |
||||
|
|
||||
|
## Proof of Completion |
||||
|
|
||||
|
- 学生可在企业无自定义项时取得通用清单;有自定义项时取得合并后的有效清单。 |
||||
|
- 学生重复设置同一完成状态不产生重复行,完成率和完成时间正确;并发冲突返回明确 409 错误码。 |
||||
|
- 企业只能操作本企业数据;跨企业 itemId、非 COMPANY 或非 STUDENT 请求返回 403/404,不泄露其他企业数据。 |
||||
|
- 公司知识响应只包含白名单字段;无企业知识内容时返回 `BASIC_ONLY` 和企业基础资料;评价少于 3 条时不返回平均分。 |
||||
|
- `mvn -Dtest=Onboarding*Test test`、`mvn clean verify`、`python docs/verify_openapi.py` 全部通过。 |
||||
|
- OpenAPI 契约包含全部 9 个路径、请求体和响应模型,且与运行时文档一致。 |
||||
@ -0,0 +1,20 @@ |
|||||
|
# N04 Spec Merge Report |
||||
|
|
||||
|
同步时间:2026-09-17T02:48:23Z |
||||
|
|
||||
|
| Capability | ADDED | MODIFIED | REMOVED | RENAMED | Status | |
||||
|
| --- | ---: | ---: | ---: | ---: | --- | |
||||
|
| onboarding-checklist | 6 | 0 | 0 | 0 | published | |
||||
|
| company-knowledge | 6 | 0 | 0 | 0 | published | |
||||
|
|
||||
|
- Total requirements published: 12 |
||||
|
- Conflicts: none |
||||
|
- Delta files retained for traceability. |
||||
|
- Manual fallback used because ssf sync is unavailable in this environment. |
||||
|
|
||||
|
## Hashes |
||||
|
|
||||
|
- specs/onboarding-checklist/spec.md: 16dfd71160065d7df6dbd5457557de14ea82177f9b6fe2c128182f8414b74cf6 |
||||
|
- changes/n04-onboard/specs/onboarding-checklist/spec.md: 22a458ea81e3081d0f427089712269f3481a91cb74f5b46295d16f369ee1a50b |
||||
|
- specs/company-knowledge/spec.md: fdbba93e72477c4616c8fa35682e3603921000e75a537c4327fbcd81ea966887 |
||||
|
- changes/n04-onboard/specs/company-knowledge/spec.md: 254cc5df3b9d758d0f26321d6554b634b75672d61331fde74c317ec8d1da4e6e |
||||
@ -0,0 +1,125 @@ |
|||||
|
# Company Knowledge |
||||
|
|
||||
|
## ADDED Requirements |
||||
|
|
||||
|
### Requirement: 学生读取公司知识介绍 |
||||
|
|
||||
|
系统 SHALL 允许已登录 STUDENT 通过 `GET /onboarding/companies/{companyId}/knowledge` 读取公司知识介绍。响应 MUST 聚合企业基础资料、企业维护的知识内容以及公开评价摘要,并使用白名单 DTO 排除 `ownerUserId` 等内部字段。 |
||||
|
|
||||
|
#### Scenario: 企业已维护知识内容 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求存在企业且该企业已有知识内容 |
||||
|
- **THEN** 系统返回企业基础资料、文化、工作制度、入职指南和更新时间 |
||||
|
- **AND** `knowledgeState` 为 `CONFIGURED` |
||||
|
- **AND** 不返回企业 `ownerUserId`、员工用户 ID、评价哈希或审核原因 |
||||
|
|
||||
|
#### Scenario: 企业未维护知识内容 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求存在企业且该企业没有知识内容记录 |
||||
|
- **THEN** 系统仍返回企业基础资料 |
||||
|
- **AND** `knowledgeState` 为 `BASIC_ONLY` |
||||
|
- **AND** 知识内容字段为 `null` 或空集合,不返回 404 |
||||
|
|
||||
|
#### Scenario: 企业不存在 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求不存在的 `companyId` |
||||
|
- **THEN** 系统返回 404 `COMPANY_NOT_FOUND` |
||||
|
|
||||
|
### Requirement: 公司知识评价摘要使用公开数据和小样本保护 |
||||
|
|
||||
|
系统 SHALL 聚合该企业全部岗位中状态为 `PUBLISHED` 且审核状态为 `AUTO_APPROVED` 或 `APPROVED` 的评价。公开评价少于 3 条时 MUST 返回 `INSUFFICIENT` 或 `EMPTY`,且不得返回平均分或维度均值。达到 3 条时 MAY 返回平均评分、评分分布、热门标签和八维均值。 |
||||
|
|
||||
|
#### Scenario: 无公开评价 |
||||
|
|
||||
|
- **WHEN** 企业没有任何符合条件的公开评价 |
||||
|
- **THEN** `reviewSummary.state` 为 `EMPTY` |
||||
|
- **AND** `totalReviews` 为 0 |
||||
|
- **AND** `averageRating` 与 `dimensionSummary` 为空 |
||||
|
|
||||
|
#### Scenario: 公开评价少于 3 条 |
||||
|
|
||||
|
- **WHEN** 企业只有 1 或 2 条符合条件的公开评价 |
||||
|
- **THEN** `reviewSummary.state` 为 `INSUFFICIENT` |
||||
|
- **AND** 系统返回真实 `totalReviews` |
||||
|
- **AND** 不返回平均分、评分分布或维度均值 |
||||
|
|
||||
|
#### Scenario: 公开评价达到 3 条 |
||||
|
|
||||
|
- **WHEN** 企业有至少 3 条符合条件的公开评价 |
||||
|
- **THEN** `reviewSummary.state` 为 `SUFFICIENT` |
||||
|
- **AND** 系统返回平均评分、评分分布、热门标签和维度汇总 |
||||
|
- **AND** 每个维度返回样本数及是否达到小样本阈值 |
||||
|
|
||||
|
### Requirement: 企业读取本企业知识配置 |
||||
|
|
||||
|
系统 SHALL 允许 COMPANY 通过 `GET /onboarding/company/knowledge` 读取本企业知识配置。无记录时 MUST 返回 `configured=false` 和空内容,而不是 404;有记录时 MUST 返回内容和当前版本。 |
||||
|
|
||||
|
#### Scenario: 尚未配置 |
||||
|
|
||||
|
- **WHEN** COMPANY 请求本企业知识配置且尚未创建记录 |
||||
|
- **THEN** 系统返回 `configured=false` |
||||
|
- **AND** 内容字段为空,版本为 `0` |
||||
|
|
||||
|
#### Scenario: 已配置 |
||||
|
|
||||
|
- **WHEN** COMPANY 请求本企业已有知识配置 |
||||
|
- **THEN** 系统返回文化、工作制度、入职指南、更新时间和当前版本 |
||||
|
- **AND** `configured=true` |
||||
|
|
||||
|
### Requirement: 企业 Upsert 公司知识内容 |
||||
|
|
||||
|
系统 SHALL 允许 COMPANY 通过 `PUT /onboarding/company/knowledge` 创建或更新本企业知识内容。字段 MUST 使用纯文本并受长度限制,不允许 HTML 富文本。更新已有记录时 MUST 使用乐观锁版本;版本冲突返回 409 `ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE`。 |
||||
|
|
||||
|
#### Scenario: 首次创建 |
||||
|
|
||||
|
- **WHEN** COMPANY 首次提交合法的知识内容 |
||||
|
- **THEN** 系统创建绑定该企业的知识记录 |
||||
|
- **AND** 返回 `configured=true` 和版本 `0` |
||||
|
|
||||
|
#### Scenario: 使用当前版本更新 |
||||
|
|
||||
|
- **WHEN** COMPANY 使用当前版本提交新的文化、制度或入职指南 |
||||
|
- **THEN** 系统更新记录并递增版本 |
||||
|
- **AND** 返回最新内容 |
||||
|
|
||||
|
#### Scenario: 使用过期版本更新 |
||||
|
|
||||
|
- **WHEN** COMPANY 使用过期版本提交知识内容 |
||||
|
- **THEN** 系统返回 409 `ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE` |
||||
|
- **AND** 不覆盖数据库中的新内容 |
||||
|
|
||||
|
#### Scenario: 非法内容 |
||||
|
|
||||
|
- **WHEN** 请求字段超出最大长度或包含不受支持的格式 |
||||
|
- **THEN** 系统返回 400 `BAD_REQUEST` |
||||
|
- **AND** 不写入知识记录 |
||||
|
|
||||
|
### Requirement: 公司知识权限与所有权隔离 |
||||
|
|
||||
|
学生读取接口 MUST 仅允许 STUDENT;企业配置接口 MUST 仅允许 COMPANY 且只操作当前用户拥有的企业。任何跨企业读取配置或修改操作 MUST 被拒绝。 |
||||
|
|
||||
|
#### Scenario: 企业访问本企业配置 |
||||
|
|
||||
|
- **WHEN** COMPANY 读取或更新自己的知识记录 |
||||
|
- **THEN** 系统允许操作 |
||||
|
|
||||
|
#### Scenario: 非企业访问企业配置 |
||||
|
|
||||
|
- **WHEN** STUDENT、ADMIN 或未登录用户访问企业知识配置接口 |
||||
|
- **THEN** 系统返回 401 或 403 |
||||
|
- **AND** 不读取或修改企业配置 |
||||
|
|
||||
|
#### Scenario: 不存在的企业 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求不存在的公司知识 |
||||
|
- **THEN** 系统返回 404 `COMPANY_NOT_FOUND` |
||||
|
|
||||
|
### Requirement: 不改变既有评价与企业接口语义 |
||||
|
|
||||
|
本变更 MUST 只读复用既有企业、岗位、评价和维度评分数据,不得修改既有表结构、响应字段或权限语义。 |
||||
|
|
||||
|
#### Scenario: 既有评价接口回归 |
||||
|
|
||||
|
- **WHEN** 执行已有评价与企业模块测试 |
||||
|
- **THEN** 所有既有测试保持通过 |
||||
|
- **AND** OpenAPI 中既有路径的请求与响应语义不变 |
||||
@ -0,0 +1,151 @@ |
|||||
|
# Onboarding Checklist |
||||
|
|
||||
|
## ADDED Requirements |
||||
|
|
||||
|
### Requirement: 学生读取有效入职清单 |
||||
|
|
||||
|
系统 SHALL 允许已登录 STUDENT 按 `companyId` 读取由通用清单与企业活动自定义项组成的有效入职清单。响应 MUST 包含企业、当前学生完成状态、完成数量、总数量、完成率、是否是企业自定义配置,以及按 `sortOrder`、`id` 稳定排序的条目。 |
||||
|
|
||||
|
#### Scenario: 企业未配置自定义项 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求存在企业的清单,且该企业没有活动自定义项 |
||||
|
- **THEN** 系统返回全部活动通用清单项 |
||||
|
- **AND** `customized` 为 `false` |
||||
|
- **AND** 每个条目返回标准来源、分类、标题、说明、必填标记和当前学生的完成状态 |
||||
|
|
||||
|
#### Scenario: 企业已配置自定义项 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求存在企业的清单,且该企业存在活动自定义项 |
||||
|
- **THEN** 系统返回活动通用项与企业自定义项 |
||||
|
- **AND** 通用项与企业项使用稳定排序,响应 `customized` 为 `true` |
||||
|
- **AND** 每个条目的 `itemId` 和 `source` 足以用于后续完成状态更新 |
||||
|
|
||||
|
#### Scenario: 企业不存在 |
||||
|
|
||||
|
- **WHEN** STUDENT 请求不存在的 `companyId` |
||||
|
- **THEN** 系统返回 404 与既有 `COMPANY_NOT_FOUND` 错误码 |
||||
|
- **AND** 不创建完成状态行 |
||||
|
|
||||
|
### Requirement: 学生更新个人清单完成状态 |
||||
|
|
||||
|
系统 SHALL 允许 STUDENT 通过 `PUT /onboarding/companies/{companyId}/checklist/items/{itemId}/completion` 将本人对指定有效条目的状态设置为完成或未完成。请求 MUST 使用绝对状态语义,重复提交同一状态 MUST 不创建重复记录。响应 MUST 返回条目状态、版本和更新后的完成率。 |
||||
|
|
||||
|
#### Scenario: 首次标记完成 |
||||
|
|
||||
|
- **WHEN** STUDENT 对本人可访问的条目提交 `completed=true` |
||||
|
- **THEN** 系统创建该学生与该条目的完成记录 |
||||
|
- **AND** 设置 `completedAt` |
||||
|
- **AND** 返回更新后的完成数量、总数量和完成率 |
||||
|
|
||||
|
#### Scenario: 重复设置相同状态 |
||||
|
|
||||
|
- **WHEN** STUDENT 对同一条目重复提交相同 `completed` 值 |
||||
|
- **THEN** 系统返回幂等成功结果 |
||||
|
- **AND** 数据库仍只有一条学生与条目完成记录 |
||||
|
- **AND** 不改变首次完成后的业务结果 |
||||
|
|
||||
|
#### Scenario: 取消完成 |
||||
|
|
||||
|
- **WHEN** STUDENT 对已完成条目的提交 `completed=false` |
||||
|
- **THEN** 系统将记录置为未完成 |
||||
|
- **AND** 清除 `completedAt` |
||||
|
- **AND** 返回更新后的完成率 |
||||
|
|
||||
|
#### Scenario: 条目不属于该企业 |
||||
|
|
||||
|
- **WHEN** STUDENT 使用属于另一企业自定义项的 `itemId` 调用更新接口 |
||||
|
- **THEN** 系统返回 404 `ONBOARDING_CHECKLIST_ITEM_NOT_FOUND` |
||||
|
- **AND** 不允许创建或修改完成记录 |
||||
|
|
||||
|
### Requirement: 企业创建自定义清单项 |
||||
|
|
||||
|
系统 SHALL 允许 COMPANY 为本企业创建自定义清单项。请求 MUST 校验标题、说明、分类、排序值和必填标记。请求携带 `idempotencyKey` 时,同一企业、同一 key 且请求体相同 MUST 返回原条目;请求体不同 MUST 返回 409 `ONBOARDING_IDEMPOTENCY_CONFLICT`。 |
||||
|
|
||||
|
#### Scenario: 创建成功 |
||||
|
|
||||
|
- **WHEN** COMPANY 提交合法的标题、分类和可选说明 |
||||
|
- **THEN** 系统创建仅属于该企业的活动条目 |
||||
|
- **AND** 返回条目 ID、来源 `COMPANY`、版本、排序值和必填标记 |
||||
|
|
||||
|
#### Scenario: 幂等重放 |
||||
|
|
||||
|
- **WHEN** COMPANY 使用相同 `idempotencyKey` 和相同请求体重复创建 |
||||
|
- **THEN** 系统返回第一次创建的条目 |
||||
|
- **AND** 数据库不产生第二条记录 |
||||
|
|
||||
|
#### Scenario: 幂等键内容冲突 |
||||
|
|
||||
|
- **WHEN** COMPANY 重复使用同一 `idempotencyKey`,但标题、分类、说明、排序或必填标记不同 |
||||
|
- **THEN** 系统返回 409 `ONBOARDING_IDEMPOTENCY_CONFLICT` |
||||
|
|
||||
|
#### Scenario: 超出企业条目上限 |
||||
|
|
||||
|
- **WHEN** COMPANY 已拥有 50 条活动自定义项并继续创建 |
||||
|
- **THEN** 系统返回 409 `ONBOARDING_ITEM_LIMIT_EXCEEDED` |
||||
|
- **AND** 不写入第 51 条活动条目 |
||||
|
|
||||
|
### Requirement: 企业查询和更新自定义清单项 |
||||
|
|
||||
|
系统 SHALL 允许 COMPANY 查询本企业全部自定义项,并通过带版本号的更新请求修改标题、说明、分类、排序、必填标记和活动状态。版本冲突 MUST 返回 409 `ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE`,不得静默覆盖。 |
||||
|
|
||||
|
#### Scenario: 查询本企业条目 |
||||
|
|
||||
|
- **WHEN** COMPANY 请求本企业自定义条目列表 |
||||
|
- **THEN** 系统返回该企业条目,包括活动与软删除条目及当前版本 |
||||
|
- **AND** 不返回其他企业的条目 |
||||
|
|
||||
|
#### Scenario: 正常更新 |
||||
|
|
||||
|
- **WHEN** COMPANY 使用当前 `version` 更新本企业条目 |
||||
|
- **THEN** 系统保存修改并递增版本 |
||||
|
- **AND** 返回更新后的条目 |
||||
|
|
||||
|
#### Scenario: 版本过期 |
||||
|
|
||||
|
- **WHEN** COMPANY 使用过期 `version` 更新条目 |
||||
|
- **THEN** 系统返回 409 `ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE` |
||||
|
- **AND** 不覆盖数据库中较新的值 |
||||
|
|
||||
|
### Requirement: 企业软删除自定义清单项 |
||||
|
|
||||
|
系统 SHALL 允许 COMPANY 通过带当前 `version` 的删除请求软删除本企业条目。软删除 MUST 只将条目标记为非活动,不物理删除条目或学生历史完成记录。重复删除已非活动条目 MUST 返回幂等成功结果。 |
||||
|
|
||||
|
#### Scenario: 首次删除 |
||||
|
|
||||
|
- **WHEN** COMPANY 使用当前版本删除本企业活动条目 |
||||
|
- **THEN** 系统将条目 `active` 置为 `false` |
||||
|
- **AND** 该条目不再出现在学生有效清单中 |
||||
|
- **AND** 既有完成记录保留 |
||||
|
|
||||
|
#### Scenario: 重复删除 |
||||
|
|
||||
|
- **WHEN** COMPANY 再次删除已软删除条目 |
||||
|
- **THEN** 系统返回成功 |
||||
|
- **AND** 不产生第二次数据变更 |
||||
|
|
||||
|
#### Scenario: 删除其他企业条目 |
||||
|
|
||||
|
- **WHEN** COMPANY 删除不属于本企业的条目 |
||||
|
- **THEN** 系统返回 404 `ONBOARDING_CHECKLIST_ITEM_NOT_FOUND` |
||||
|
- **AND** 目标条目保持原状态 |
||||
|
|
||||
|
### Requirement: 清单权限和数据最小化 |
||||
|
|
||||
|
学生接口 MUST 仅允许 STUDENT;企业配置接口 MUST 仅允许 COMPANY 且操作绑定当前用户拥有的企业。所有响应 MUST 使用白名单 DTO,不返回用户密码、Token、IP、User-Agent、评价审核原因或其他企业内部的非必要字段。 |
||||
|
|
||||
|
#### Scenario: 非学生访问学生清单 |
||||
|
|
||||
|
- **WHEN** COMPANY、ADMIN 或未登录用户访问学生清单接口 |
||||
|
- **THEN** 系统返回 401 或 403 |
||||
|
- **AND** 不返回清单或完成状态 |
||||
|
|
||||
|
#### Scenario: 非企业访问企业配置 |
||||
|
|
||||
|
- **WHEN** STUDENT、ADMIN 或未登录用户访问企业配置接口 |
||||
|
- **THEN** 系统返回 401 或 403 |
||||
|
- **AND** 不创建或修改企业条目 |
||||
|
|
||||
|
#### Scenario: 企业无绑定资料 |
||||
|
|
||||
|
- **WHEN** COMPANY 用户尚未创建企业资料却调用企业配置接口 |
||||
|
- **THEN** 系统返回 404 `COMPANY_NOT_FOUND` |
||||
@ -0,0 +1,47 @@ |
|||||
|
# n04-onboard 实施任务 |
||||
|
|
||||
|
## Delivery / Proof Map |
||||
|
|
||||
|
| Requirement Area | Tasks | Evidence | |
||||
|
|---|---|---| |
||||
|
| 通用/企业清单数据模型与种子 | 1, 2 | 迁移测试、Repository 集成测试 | |
||||
|
| 学生有效清单与完成状态 | 2, 3, 6 | Service 单元测试、MockMvc、持久化测试 | |
||||
|
| 企业自定义项 CRUD、幂等、并发 | 2, 4, 6 | Service 单元测试、MockMvc、Repository 集成测试 | |
||||
|
| 公司知识配置与基础信息回退 | 1, 2, 5 | Service 单元测试、MockMvc、持久化测试 | |
||||
|
| 公开评价摘要与小样本保护 | 2, 5 | 聚合 Service 测试、评价边界测试 | |
||||
|
| 权限、白名单响应和错误码 | 6, 7 | 未登录、角色、跨企业、脱敏断言 | |
||||
|
| OpenAPI、前端联调和文档 | 8 | `python docs/verify_openapi.py`、文档检查 | |
||||
|
| 全量回归和可发布证明 | 8 | `mvn clean verify`、专项测试汇总 | |
||||
|
|
||||
|
## Tasks |
||||
|
|
||||
|
- [ ] 1. 新增 V38 建表迁移与 V39 通用清单种子,路径为 `mydreamjob-server/src/main/resources/db/migration/`。可观察结果:MySQL/H2 均可创建 `onboarding_checklist_items`、`student_onboarding_progress`、`company_knowledge`,主外键、唯一键和索引存在;通用清单按分类和稳定排序写入。证据:`mvn -Dtest=OnboardingMigrationTest,OnboardingSeedDataIntegrationTest test`。依赖:无。 |
||||
|
- [ ] 2. 新增 `com.mydreamjob.onboarding` 领域实体与 Repository,路径为 `mydreamjob-server/src/main/java/com/mydreamjob/onboarding/`。可观察结果:可查询某企业的活动通用+自定义项、批量查询当前学生完成记录、按企业查询知识记录,并为评价聚合提供跨岗位只读查询。证据:`mvn -Dtest=OnboardingRepositoryIntegrationTest test`。依赖:1。 |
||||
|
- [ ] 3. 实现学生有效清单查询与完成状态更新服务及接口,路径为 `OnboardingChecklistService.java`、`OnboardingChecklistController.java` 和对应 DTO。可观察结果:无企业自定义项时返回通用清单,有自定义项时合并排序;PUT 支持完成/取消完成、重复同状态幂等、完成率与完成时间正确。证据:`mvn -Dtest=OnboardingChecklistServiceTest,OnboardingChecklistControllerTest test`。依赖:2。 |
||||
|
- [ ] 4. 实现企业自定义清单项查询、创建、更新和软删除,路径为 `OnboardingChecklistService.java`、`OnboardingChecklistController.java` 和对应 DTO。可观察结果:仅企业本人可操作;创建支持 `idempotencyKey` 重放与请求体哈希冲突;更新/删除使用版本号;软删除后条目从学生有效清单消失但历史完成记录保留。证据:`mvn -Dtest=OnboardingCompanyChecklistServiceTest,OnboardingCompanyChecklistControllerTest test`。依赖:1, 2。 |
||||
|
- [ ] 5. 实现企业知识内容 Upsert 和学生公司知识聚合,路径为 `CompanyKnowledgeService.java`、`CompanyKnowledgeController.java` 和对应 DTO。可观察结果:企业读取无记录时返回 `configured=false`;学生读取无知识时返回 `BASIC_ONLY`;公开评价少于 3 条不返回均值;达到 3 条时返回平均分、分布、标签和八维摘要。证据:`mvn -Dtest=CompanyKnowledgeServiceTest,CompanyKnowledgeControllerTest test`。依赖:2。 |
||||
|
- [ ] 6. 接入角色、所有权、错误码、幂等和乐观锁,路径为 `SecurityConfig.java`、`ErrorCode.java`、`OnboardingChecklistService.java`、`CompanyKnowledgeService.java`。可观察结果:`/onboarding/**` 仅认证访问;STUDENT/COMPANY 角色和公司所有权严格隔离;9701-9706 在目标冲突场景返回 409/404;响应 DTO 不包含内部身份或审核字段。证据:`mvn -Dtest=OnboardingAuthorizationIntegrationTest,OnboardingConcurrencyIntegrationTest,OnboardingPrivacyTest test`。依赖:3, 4, 5。 |
||||
|
- [ ] 7. 补齐模块测试,路径为 `mydreamjob-server/src/test/java/com/mydreamjob/onboarding/`。可观察结果:Service、MockMvc、持久化、迁移、种子、权限、幂等、并发、回退、小样本和端到端场景全部覆盖;测试断言响应状态、错误码、数据库行数和排序。证据:`mvn -Dtest=Onboarding*Test test`。依赖:3, 4, 5, 6。 |
||||
|
- [ ] 8. 更新 OpenAPI、接口文档、前端联调说明和路线图,路径为 `docs/openapi-rc.json`、`docs/onboarding-api.md`、`docs/frontend-integration.md`、`docs/backend-development-roadmap.md`、`README.md`。可观察结果:9 个路径及 DTO 与运行时一致;前端文档给出请求示例、错误码、版本和幂等说明;路线图将本模块标记为实现完成/待验收。证据:`python docs/verify_openapi.py`,随后执行 `mvn clean verify`。依赖:3, 4, 5, 6, 7。 |
||||
|
|
||||
|
## Requirement-to-Task Coverage |
||||
|
|
||||
|
| Spec Requirement | Covered By | |
||||
|
|---|---| |
||||
|
| 学生读取有效入职清单 | 1, 2, 3 | |
||||
|
| 学生更新个人清单完成状态 | 2, 3, 6, 7 | |
||||
|
| 企业创建自定义清单项 | 1, 2, 4, 6, 7 | |
||||
|
| 企业查询和更新自定义清单项 | 2, 4, 6, 7 | |
||||
|
| 企业软删除自定义清单项 | 2, 4, 6, 7 | |
||||
|
| 清单权限和数据最小化 | 3, 4, 6, 7 | |
||||
|
| 学生读取公司知识介绍 | 1, 2, 5, 7 | |
||||
|
| 公司知识评价摘要与小样本保护 | 2, 5, 7 | |
||||
|
| 企业读取本企业知识配置 | 1, 2, 5, 7 | |
||||
|
| 企业 Upsert 公司知识内容 | 1, 2, 5, 6, 7 | |
||||
|
| 公司知识权限与所有权隔离 | 5, 6, 7 | |
||||
|
| 不改变既有评价与企业接口语义 | 7, 8 | |
||||
|
|
||||
|
## Completion Gate |
||||
|
|
||||
|
- 所有任务完成且专项测试、全量测试、OpenAPI 校验通过后才能进入 release-archivist。 |
||||
|
- 不允许降低既有接口语义、删除业务数据或跳过迁移验证。 |
||||
@ -0,0 +1,25 @@ |
|||||
|
# N04 Verification Report |
||||
|
|
||||
|
验证时间:2026-09-17T01:53:38Z |
||||
|
|
||||
|
## Outcome |
||||
|
|
||||
|
通过并带正式豁免。N04 范围内验证通过;仓库全量测试中的既有非 N04 失败已由用户正式豁免;真实 MySQL/Docker 验收保留为发布前门禁。 |
||||
|
|
||||
|
## Passed |
||||
|
|
||||
|
- N04 专项测试:96/96。 |
||||
|
- 并发边界测试:PASS。 |
||||
|
- 随机顺序端到端测试:7/7。 |
||||
|
- OpenAPI 静态校验:PASS,135 paths / 172 operations,其中 N04 6 paths / 9 operations。 |
||||
|
- 可执行 JAR 打包:PASS。 |
||||
|
|
||||
|
## Blocked / Conditional |
||||
|
|
||||
|
- mvn clean verify:1492 tests,2 failures,9 errors;失败均在 N04 范围外。 |
||||
|
- 真实 MySQL 8.4 / Docker 黑盒验收:未执行。 |
||||
|
- 正式豁免已记录;归档确认仍待 DP-7。 |
||||
|
|
||||
|
## Required Approval |
||||
|
|
||||
|
用户已批准正式豁免。允许进入 delta spec 合并和 DP-7;真实 MySQL/Docker 验收仍需在发布前完成。 |
||||
@ -0,0 +1,48 @@ |
|||||
|
# .spec-superflow.yaml — lightweight state machine |
||||
|
# Derived data. Always rebuildable from artifacts. Lost/corrupt → fall back to content-level detection. |
||||
|
|
||||
|
# === Core state === |
||||
|
state: closing |
||||
|
workflow: full |
||||
|
revision: 1 |
||||
|
|
||||
|
# === Hashes (fast staleness detection) === |
||||
|
artifacts_hash: sha256:5e15964e215b2645ea2679d15626cf2cb04fba15b8285049fc4ba8104d7ad330 |
||||
|
contract_hash: sha256:c85abc5d5ddd79b03da8ff42816436f4389bb99a5739d4fcb2a960c3c81867b1 |
||||
|
|
||||
|
# === Execution progress === |
||||
|
execution_mode: sdd |
||||
|
execution_plan_hash: sha256:b04ca4af0927fa94d7c56a462549a5edb6717e542ec30228c0f0349848cd631c |
||||
|
execution_plan_revision: 1 |
||||
|
batches_completed: 4 |
||||
|
test_result: pass |
||||
|
spec_merged: true |
||||
|
spec_publication_receipt: eyJ2ZXJzaW9uIjoxLCJzb3VyY2VfaGFzaCI6InNoYTI1NjozOTkxMDk2Zjk4ZTU1MGJhNTZhMmQ4YTY1YWZjZjFmNzUxNTc4OGNlZTM3YTYwMmVkYmZmOGNiNTcxZmYwNjM0IiwiYmFzZWxpbmVfYmVmb3JlX2hhc2giOiJzaGEyNTY6ZDM4YjQyMzcwODdhYTRkNTJkNWMyNTBkY2M4MmYwN2FjNDAyOGYzMWJmMDhiNDljMjczNDJhYTJkMGY2MjRhNiIsImJhc2VsaW5lX2FmdGVyX2hhc2giOiJzaGEyNTY6NzM4ZWNiYjFkYTRjNTM4MDIzMTFjZjM0ZWU1NzBlYjQ3NDU5OWNmMTRhZGEwZWRmMzY0MDE2NmU3M2IyNjFlZiIsImNhcGFiaWxpdGllcyI6WyJzYWxhcnktY2FsY3VsYXRpb24iLCJzYWxhcnktaGlzdG9yeSJdfQ |
||||
|
|
||||
|
# === Metadata === |
||||
|
change_name: n05-salary-calc |
||||
|
last_transition: 2026-09-21T05:09:14.898Z |
||||
|
last_transition_from: executing |
||||
|
last_transition_to: closing |
||||
|
|
||||
|
# === Decision points === |
||||
|
dp_0_decisions: 变更名称:n05-salary-calc;一句话意图:为已登录 STUDENT 提供按地级行政区配置的城市默认参数、用户覆盖、到手工资估算和历史快照能力。workflow_path=full;选择原因:新增模块、V40/V41 迁移、六个 API、权限、幂等与跨模块注销清理。范围:仅后端;不支持匿名、ADMIN、前端、个人数据导出、薪资参考或 Offer 关联。计算:税前月薪、个人五险一金简化模型、累计预扣;全国目标城市全量配置;历史物理删除,账号注销时清理。约束:城市级不拆区县;用户覆盖优先;政策版本校验;仅 STUDENT;日志不记录金额。优化:保持聚焦,不纳入前端、企业成本、年终奖、年度汇算、管理员维护和薪资大数据。沟通方式:四类规划工件一次生成,完成验证后统一进入 DP-2 审查。artifact_language=zh-CN |
||||
|
dp_0_result: confirmed |
||||
|
dp_0_confirmed: true |
||||
|
dp_0_timestamp: 2026-09-20T07:28:51Z |
||||
|
dp_1_result: confirmed: 后端薪资计算器范围、计算口径、城市政策模型、历史记录生命周期、接口、错误码、隐私与验收标准均已确认 |
||||
|
dp_1_timestamp: 2026-09-20T07:29:03Z |
||||
|
dp_2_result: approved: layered CITY/PROVINCE_FALLBACK data strategy, updated specs/design/tasks |
||||
|
dp_2_timestamp: 2026-09-20T08:15:41Z |
||||
|
dp_3_result: approved: updated execution contract with layered city policy data and dataLevel propagation |
||||
|
dp_3_timestamp: 2026-09-20T08:15:41Z |
||||
|
dp_4_result: sdd: plan revision 1; user-confirmed; 任务勾选已完成,重建当前工件哈希并保留同一 W1-W4 执行范围 |
||||
|
dp_4_timestamp: 2026-09-20T09:13:10.032Z |
||||
|
dp_5_result: null |
||||
|
dp_5_timestamp: null |
||||
|
dp_5_decisions: null |
||||
|
dp_5_confirmed: null |
||||
|
dp_6_result: conditional: N05 salary calculator verified; pre-existing 2 failures/9 errors outside N05 conditionally accepted |
||||
|
dp_6_timestamp: 2026-09-21T05:09:12Z |
||||
|
dp_7_result: confirmed: N05 salary calculator archived; branch feat/n05-salary-calc ready for merge |
||||
|
dp_7_timestamp: 2026-09-21T05:09:12Z |
||||
@ -0,0 +1,155 @@ |
|||||
|
{ |
||||
|
"mode": "sdd", |
||||
|
"source": "user-confirmed", |
||||
|
"rationale": "分层城市数据方案已批准;重新建立当前 W1-W4 执行计划", |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:c7e046b12a81b409ff08716e34062a9a4312a0af4cc20bdbd0442a7bea9e2b1e", |
||||
|
"contract_hash": "sha256:c85abc5d5ddd79b03da8ff42816436f4389bb99a5739d4fcb2a960c3c81867b1", |
||||
|
"workflow": "full", |
||||
|
"revision": 1, |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"recommendation_receipt": { |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:c7e046b12a81b409ff08716e34062a9a4312a0af4cc20bdbd0442a7bea9e2b1e", |
||||
|
"contract_hash": "sha256:c85abc5d5ddd79b03da8ff42816436f4389bb99a5739d4fcb2a960c3c81867b1", |
||||
|
"workflow": "full", |
||||
|
"execution_plan_revision_at_recommendation": null, |
||||
|
"created_at": "2026-09-20T08:16:23.723Z", |
||||
|
"hash": "sha256:731967dc9f49d233bbf9bb22b2cab7ce087e69893333490aa8c9142e5bdf16ed" |
||||
|
}, |
||||
|
"selection": { |
||||
|
"confirmed": true, |
||||
|
"followed_recommendation": true, |
||||
|
"acknowledged_non_recommendation": false |
||||
|
}, |
||||
|
"hash": "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
} |
||||
@ -0,0 +1,155 @@ |
|||||
|
{ |
||||
|
"mode": "sdd", |
||||
|
"source": "user-confirmed-revision", |
||||
|
"rationale": "规范化 tasks.md 任务编号以兼容 checkpoint CLI;W1-W4 范围与依赖不变", |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:2820966ebe5197fbfd6dfc5c18ff31dc2a11fc6170510bdc35f00a82d1f0b684", |
||||
|
"contract_hash": "sha256:ce264c979b71f9f9b4d95881dd1f3fd8a1d62c3d848fd508e26cc5ce5b76d3c0", |
||||
|
"workflow": "full", |
||||
|
"revision": 2, |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"recommendation_receipt": { |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:2820966ebe5197fbfd6dfc5c18ff31dc2a11fc6170510bdc35f00a82d1f0b684", |
||||
|
"contract_hash": "sha256:ce264c979b71f9f9b4d95881dd1f3fd8a1d62c3d848fd508e26cc5ce5b76d3c0", |
||||
|
"workflow": "full", |
||||
|
"execution_plan_revision_at_recommendation": 1, |
||||
|
"created_at": "2026-09-20T07:55:10.974Z", |
||||
|
"hash": "sha256:a57b53ceb85db7eb53d357bbca5af289cd06b89cbfc1f41d73319bdc41289368" |
||||
|
}, |
||||
|
"selection": { |
||||
|
"confirmed": true, |
||||
|
"followed_recommendation": true, |
||||
|
"acknowledged_non_recommendation": false |
||||
|
}, |
||||
|
"hash": "sha256:eddde134568a3cf09036f21af22f716d287f8e960ca5f23d96bd01a32a4f8511" |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
N05 disposable Docker MySQL black-box evidence |
||||
|
MySQL image: mysql:8.4 |
||||
|
Backend image: eclipse-temurin:17-jre |
||||
|
Flyway versions: 40,41 |
||||
|
City policy count: 359 |
||||
|
Data levels: CITY 12 |
||||
|
Additional level row: PROVINCE_FALLBACK 347 |
||||
|
API black-box: PASS: N05 salary calculator disposable-MySQL black box |
||||
|
Containers were destroyed after evidence capture. |
||||
@ -0,0 +1,155 @@ |
|||||
|
{ |
||||
|
"mode": "sdd", |
||||
|
"source": "user-confirmed", |
||||
|
"rationale": "任务勾选已完成,重建当前工件哈希并保留同一 W1-W4 执行范围", |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:5e15964e215b2645ea2679d15626cf2cb04fba15b8285049fc4ba8104d7ad330", |
||||
|
"contract_hash": "sha256:c85abc5d5ddd79b03da8ff42816436f4389bb99a5739d4fcb2a960c3c81867b1", |
||||
|
"workflow": "full", |
||||
|
"revision": 1, |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"recommendation_receipt": { |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:5e15964e215b2645ea2679d15626cf2cb04fba15b8285049fc4ba8104d7ad330", |
||||
|
"contract_hash": "sha256:c85abc5d5ddd79b03da8ff42816436f4389bb99a5739d4fcb2a960c3c81867b1", |
||||
|
"workflow": "full", |
||||
|
"execution_plan_revision_at_recommendation": null, |
||||
|
"created_at": "2026-09-20T09:13:09.901Z", |
||||
|
"hash": "sha256:a723f2d8fda600c00e76671ae631e81aaa425cf2d0e18d0a9060ba07776e1bd8" |
||||
|
}, |
||||
|
"selection": { |
||||
|
"confirmed": true, |
||||
|
"followed_recommendation": true, |
||||
|
"acknowledged_non_recommendation": false |
||||
|
}, |
||||
|
"hash": "sha256:b04ca4af0927fa94d7c56a462549a5edb6717e542ec30228c0f0349848cd631c" |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
{ |
||||
|
"recommendation": { |
||||
|
"available_modes": [ |
||||
|
"inline", |
||||
|
"batch-inline", |
||||
|
"sdd" |
||||
|
], |
||||
|
"recommendation": { |
||||
|
"mode": "sdd", |
||||
|
"reasons": [ |
||||
|
"The declared work spans multiple waves, so SDD keeps dependencies and review receipts explicit." |
||||
|
] |
||||
|
}, |
||||
|
"facts": { |
||||
|
"workflow": "full", |
||||
|
"documented_task_count": 9, |
||||
|
"planned_task_count": 9, |
||||
|
"planned_wave_count": 4, |
||||
|
"has_parallel_wave": false, |
||||
|
"inline_threshold": 3 |
||||
|
} |
||||
|
}, |
||||
|
"waves": [ |
||||
|
{ |
||||
|
"id": "W1", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"1", |
||||
|
"2" |
||||
|
], |
||||
|
"depends_on": [] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W2", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"3", |
||||
|
"4" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W1" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W3", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"5", |
||||
|
"6", |
||||
|
"7" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W2" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "W4", |
||||
|
"strategy": "serial", |
||||
|
"tasks": [ |
||||
|
"8", |
||||
|
"9" |
||||
|
], |
||||
|
"depends_on": [ |
||||
|
"W3" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"artifacts_hash": "sha256:5e15964e215b2645ea2679d15626cf2cb04fba15b8285049fc4ba8104d7ad330", |
||||
|
"contract_hash": "sha256:c85abc5d5ddd79b03da8ff42816436f4389bb99a5739d4fcb2a960c3c81867b1", |
||||
|
"workflow": "full", |
||||
|
"execution_plan_revision_at_recommendation": null, |
||||
|
"created_at": "2026-09-20T09:13:09.901Z", |
||||
|
"hash": "sha256:a723f2d8fda600c00e76671ae631e81aaa425cf2d0e18d0a9060ba07776e1bd8" |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "2" |
||||
|
task_hash: "sha256:4395a095f2f5a85abccfe61bca9647cdc1a92ad9febef404ff8726432b4e25f8" |
||||
|
next: "Wave W2 task 3: city policy query service/controller/DTOs" |
||||
|
completed: "V41 layered seed: 359 cities, 12 CITY, 347 PROVINCE_FALLBACK" |
||||
|
evidence: "mvn -Dtest=SalaryCityPolicySeedTest,SalaryMigrationTest,SalaryRepositoryIntegrationTest test: 3/3 passed" |
||||
|
review: "changes/n05-salary-calc/.superpowers/sdd/reviews/T2.md" |
||||
|
risk: "Fallback rows require explicit disclosure and later city-level verification" |
||||
|
commit_start: "79f0646da8a2fa027b8ef9e025ae1096b8ed3903" |
||||
|
commit_end: "2d315f90e46982e9658da6483f46e2acbe21bcca" |
||||
|
created_at: "2026-09-20T08:23:29.908Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 2 |
||||
|
|
||||
|
## Next |
||||
|
Wave W2 task 3: city policy query service/controller/DTOs |
||||
|
|
||||
|
## Completed |
||||
|
V41 layered seed: 359 cities, 12 CITY, 347 PROVINCE_FALLBACK |
||||
|
|
||||
|
## Evidence |
||||
|
mvn -Dtest=SalaryCityPolicySeedTest,SalaryMigrationTest,SalaryRepositoryIntegrationTest test: 3/3 passed |
||||
|
|
||||
|
## Review |
||||
|
changes/n05-salary-calc/.superpowers/sdd/reviews/T2.md |
||||
|
|
||||
|
## Risk |
||||
|
Fallback rows require explicit disclosure and later city-level verification |
||||
|
|
||||
|
## Commit Start |
||||
|
79f0646da8a2fa027b8ef9e025ae1096b8ed3903 |
||||
|
|
||||
|
## Commit End |
||||
|
2d315f90e46982e9658da6483f46e2acbe21bcca |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "3" |
||||
|
task_hash: "sha256:b5aaa1f3ea977fa289582c0e7e29adc5990fc4c2f46a5e64c6f142b391dec368" |
||||
|
next: "Task 4: implement BigDecimal salary calculation engine" |
||||
|
completed: "City list/detail APIs with STUDENT authorization and dataLevel" |
||||
|
evidence: "mvn -Dtest=SalaryCityPolicyServiceTest,SalaryCityPolicyControllerTest test: 9/9 passed" |
||||
|
review: ".superpowers/sdd/reviews/T3.md" |
||||
|
risk: "Fallback city detail must remain visibly marked" |
||||
|
commit_start: "2d315f90e46982e9658da6483f46e2acbe21bcca" |
||||
|
commit_end: "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7" |
||||
|
created_at: "2026-09-20T08:29:09.441Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 3 |
||||
|
|
||||
|
## Next |
||||
|
Task 4: implement BigDecimal salary calculation engine |
||||
|
|
||||
|
## Completed |
||||
|
City list/detail APIs with STUDENT authorization and dataLevel |
||||
|
|
||||
|
## Evidence |
||||
|
mvn -Dtest=SalaryCityPolicyServiceTest,SalaryCityPolicyControllerTest test: 9/9 passed |
||||
|
|
||||
|
## Review |
||||
|
.superpowers/sdd/reviews/T3.md |
||||
|
|
||||
|
## Risk |
||||
|
Fallback city detail must remain visibly marked |
||||
|
|
||||
|
## Commit Start |
||||
|
2d315f90e46982e9658da6483f46e2acbe21bcca |
||||
|
|
||||
|
## Commit End |
||||
|
4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7 |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "4" |
||||
|
task_hash: "sha256:d571027295959410fd431fe86fc37d769aa15e71d4e14fcb4e5c624b8960c193" |
||||
|
next: "Wave W3 task 5: calculation persistence and policy version validation" |
||||
|
completed: "BigDecimal calculation engine with cumulative withholding and override validation" |
||||
|
evidence: "mvn -Dtest=SalaryCalculationEngineTest test: 6/6 passed" |
||||
|
review: ".superpowers/sdd/reviews/T4.md" |
||||
|
risk: "Tax anchor cases will be expanded in W4" |
||||
|
commit_start: "2d315f90e46982e9658da6483f46e2acbe21bcca" |
||||
|
commit_end: "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7" |
||||
|
created_at: "2026-09-20T08:29:09.537Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 4 |
||||
|
|
||||
|
## Next |
||||
|
Wave W3 task 5: calculation persistence and policy version validation |
||||
|
|
||||
|
## Completed |
||||
|
BigDecimal calculation engine with cumulative withholding and override validation |
||||
|
|
||||
|
## Evidence |
||||
|
mvn -Dtest=SalaryCalculationEngineTest test: 6/6 passed |
||||
|
|
||||
|
## Review |
||||
|
.superpowers/sdd/reviews/T4.md |
||||
|
|
||||
|
## Risk |
||||
|
Tax anchor cases will be expanded in W4 |
||||
|
|
||||
|
## Commit Start |
||||
|
2d315f90e46982e9658da6483f46e2acbe21bcca |
||||
|
|
||||
|
## Commit End |
||||
|
4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7 |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "5" |
||||
|
task_hash: "sha256:3d4c3ad3efc8a298211c74dd6c8da2d95fe4a8c1269fc58f0672ccc5f148e3c6" |
||||
|
next: "Task 6: history list/detail/delete" |
||||
|
completed: "Calculation save, policy version and idempotency" |
||||
|
evidence: "W3 suite 6/6 passed" |
||||
|
review: ".superpowers/sdd/reviews/T5.md" |
||||
|
risk: "Concurrent same-key race needs MySQL validation" |
||||
|
commit_start: "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7" |
||||
|
commit_end: "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b" |
||||
|
created_at: "2026-09-20T08:34:50.804Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 5 |
||||
|
|
||||
|
## Next |
||||
|
Task 6: history list/detail/delete |
||||
|
|
||||
|
## Completed |
||||
|
Calculation save, policy version and idempotency |
||||
|
|
||||
|
## Evidence |
||||
|
W3 suite 6/6 passed |
||||
|
|
||||
|
## Review |
||||
|
.superpowers/sdd/reviews/T5.md |
||||
|
|
||||
|
## Risk |
||||
|
Concurrent same-key race needs MySQL validation |
||||
|
|
||||
|
## Commit Start |
||||
|
4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7 |
||||
|
|
||||
|
## Commit End |
||||
|
27dbc095e0413f9566bcc4ddc3bdb4f642535a6b |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "6" |
||||
|
task_hash: "sha256:ee793f06e3bc8e83ed5836909b204f7234a68754d4ce938ae9fd927be426c7e6" |
||||
|
next: "Task 7: authorization, logging privacy and account deletion cleanup" |
||||
|
completed: "History list/detail/delete and snapshot responses" |
||||
|
evidence: "W3 suite 6/6 passed" |
||||
|
review: ".superpowers/sdd/reviews/T6.md" |
||||
|
risk: "Snapshot JSON compatibility requires MySQL validation" |
||||
|
commit_start: "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7" |
||||
|
commit_end: "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b" |
||||
|
created_at: "2026-09-20T08:34:50.905Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 6 |
||||
|
|
||||
|
## Next |
||||
|
Task 7: authorization, logging privacy and account deletion cleanup |
||||
|
|
||||
|
## Completed |
||||
|
History list/detail/delete and snapshot responses |
||||
|
|
||||
|
## Evidence |
||||
|
W3 suite 6/6 passed |
||||
|
|
||||
|
## Review |
||||
|
.superpowers/sdd/reviews/T6.md |
||||
|
|
||||
|
## Risk |
||||
|
Snapshot JSON compatibility requires MySQL validation |
||||
|
|
||||
|
## Commit Start |
||||
|
4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7 |
||||
|
|
||||
|
## Commit End |
||||
|
27dbc095e0413f9566bcc4ddc3bdb4f642535a6b |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "7" |
||||
|
task_hash: "sha256:a3bf97aab3db413f757f95db0c0698d2a2041fe87309be003ccff6f2a267f162" |
||||
|
next: "Wave W4 task 8: full module tests, OpenAPI and docs" |
||||
|
completed: "Authorization, privacy boundaries and account deletion purge" |
||||
|
evidence: "W3 suite 6/6 passed" |
||||
|
review: ".superpowers/sdd/reviews/T7.md" |
||||
|
risk: "Legacy deletion tests must remain green after constructor change" |
||||
|
commit_start: "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7" |
||||
|
commit_end: "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b" |
||||
|
created_at: "2026-09-20T08:34:51.016Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 7 |
||||
|
|
||||
|
## Next |
||||
|
Wave W4 task 8: full module tests, OpenAPI and docs |
||||
|
|
||||
|
## Completed |
||||
|
Authorization, privacy boundaries and account deletion purge |
||||
|
|
||||
|
## Evidence |
||||
|
W3 suite 6/6 passed |
||||
|
|
||||
|
## Review |
||||
|
.superpowers/sdd/reviews/T7.md |
||||
|
|
||||
|
## Risk |
||||
|
Legacy deletion tests must remain green after constructor change |
||||
|
|
||||
|
## Commit Start |
||||
|
4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7 |
||||
|
|
||||
|
## Commit End |
||||
|
27dbc095e0413f9566bcc4ddc3bdb4f642535a6b |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "8" |
||||
|
task_hash: "sha256:2853a7746ebea9c39b2f8b1256abf759359c0d68f8581073655e43c461a23be0" |
||||
|
next: "Task 9: disposable Docker MySQL black-box acceptance; blocked by missing Docker runtime" |
||||
|
completed: "Salary 24/24 tests, OpenAPI 139/178, API/frontend/roadmap docs, disposable-container black-box script" |
||||
|
evidence: "mvn -Dtest=Salary*Test test: 24/24; python docs/verify_openapi.py: pass" |
||||
|
review: ".superpowers/sdd/reviews/W4-blocked.md" |
||||
|
risk: "Docker CLI/service absent; resident MySQL 3307 prohibited for W4" |
||||
|
commit_start: "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b" |
||||
|
commit_end: "c93b0d61fe547844f6ba29b74457670ed7f00c93" |
||||
|
created_at: "2026-09-20T08:56:41.205Z" |
||||
|
plan_hash: "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5" |
||||
|
plan_revision: 1 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 8 |
||||
|
|
||||
|
## Next |
||||
|
Task 9: disposable Docker MySQL black-box acceptance; blocked by missing Docker runtime |
||||
|
|
||||
|
## Completed |
||||
|
Salary 24/24 tests, OpenAPI 139/178, API/frontend/roadmap docs, disposable-container black-box script |
||||
|
|
||||
|
## Evidence |
||||
|
mvn -Dtest=Salary*Test test: 24/24; python docs/verify_openapi.py: pass |
||||
|
|
||||
|
## Review |
||||
|
.superpowers/sdd/reviews/W4-blocked.md |
||||
|
|
||||
|
## Risk |
||||
|
Docker CLI/service absent; resident MySQL 3307 prohibited for W4 |
||||
|
|
||||
|
## Commit Start |
||||
|
27dbc095e0413f9566bcc4ddc3bdb4f642535a6b |
||||
|
|
||||
|
## Commit End |
||||
|
c93b0d61fe547844f6ba29b74457670ed7f00c93 |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "36c0214723a3fdd1714ff7923a4f38de07d7b042", |
||||
|
"head": "2d315f90e46982e9658da6483f46e2acbe21bcca", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W1.md", |
||||
|
"report_sha256": "sha256:afb12884b78fdfd3b0edb199900c84f19a130e8bb95c1a680255d59aee43343c", |
||||
|
"plan_hash": "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T08:23:35.131Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "2d315f90e46982e9658da6483f46e2acbe21bcca", |
||||
|
"head": "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W2.md", |
||||
|
"report_sha256": "sha256:2e4ac6255d2c08931102f75b414b24552c6fe2272b3bc139a91e8c4c77215bbe", |
||||
|
"plan_hash": "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T08:29:10.010Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7", |
||||
|
"head": "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W3.md", |
||||
|
"report_sha256": "sha256:7d2823941f43fd101c1271be77703e6ae614650c64c7c1fe1484a7cae41f68fa", |
||||
|
"plan_hash": "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T08:34:51.607Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b", |
||||
|
"head": "ed212c8e6b44688ce9e7fd9df36e724f5977559e", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W4.md", |
||||
|
"report_sha256": "sha256:2e6fa11d7eca063a80d97e3edc27b21763f285e9f6575e45db57b4c670d51972", |
||||
|
"plan_hash": "sha256:77341d6e000ef1624d59d66b8b361c24e7ef8f1b5d065dd4d648a2a7537e72d5", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T09:12:19.493Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "36c0214723a3fdd1714ff7923a4f38de07d7b042", |
||||
|
"head": "2d315f90e46982e9658da6483f46e2acbe21bcca", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W1.md", |
||||
|
"report_sha256": "sha256:afb12884b78fdfd3b0edb199900c84f19a130e8bb95c1a680255d59aee43343c", |
||||
|
"plan_hash": "sha256:b04ca4af0927fa94d7c56a462549a5edb6717e542ec30228c0f0349848cd631c", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T09:13:15.161Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "2d315f90e46982e9658da6483f46e2acbe21bcca", |
||||
|
"head": "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W2.md", |
||||
|
"report_sha256": "sha256:2e4ac6255d2c08931102f75b414b24552c6fe2272b3bc139a91e8c4c77215bbe", |
||||
|
"plan_hash": "sha256:b04ca4af0927fa94d7c56a462549a5edb6717e542ec30228c0f0349848cd631c", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T09:13:15.620Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "4b42a624d7ec2bb5cac64bc13cd7d4d8eba91ee7", |
||||
|
"head": "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W3.md", |
||||
|
"report_sha256": "sha256:7d2823941f43fd101c1271be77703e6ae614650c64c7c1fe1484a7cae41f68fa", |
||||
|
"plan_hash": "sha256:b04ca4af0927fa94d7c56a462549a5edb6717e542ec30228c0f0349848cd631c", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T09:13:16.077Z" |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"status": "pass", |
||||
|
"base": "27dbc095e0413f9566bcc4ddc3bdb4f642535a6b", |
||||
|
"head": "ed212c8e6b44688ce9e7fd9df36e724f5977559e", |
||||
|
"report": ".superpowers\\sdd\\reviews\\W4.md", |
||||
|
"report_sha256": "sha256:2e6fa11d7eca063a80d97e3edc27b21763f285e9f6575e45db57b4c670d51972", |
||||
|
"plan_hash": "sha256:b04ca4af0927fa94d7c56a462549a5edb6717e542ec30228c0f0349848cd631c", |
||||
|
"plan_revision": 1, |
||||
|
"recorded_at": "2026-09-20T09:13:16.534Z" |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
task_id: "1" |
||||
|
task_hash: "sha256:d6169a29f309b7713b328adf69ad846596c52c63b9ebb92d256b47d444404bc0" |
||||
|
next: "Task 2: create V41 full city policy seed and seed validation tests" |
||||
|
completed: "V40 schema, entities, repositories, migration and repository tests" |
||||
|
evidence: "mvn -Dtest=SalaryMigrationTest,SalaryRepositoryIntegrationTest test: 2/2 passed" |
||||
|
review: "changes/n05-salary-calc/.superpowers/sdd/reviews/T1.md" |
||||
|
risk: "V41 full city policy data collection remains a release gate" |
||||
|
commit_start: "36c0214723a3fdd1714ff7923a4f38de07d7b042" |
||||
|
commit_end: "837c0632c66e6cb9a00e522944f73045258557d6" |
||||
|
created_at: "2026-09-20T07:55:14.538Z" |
||||
|
plan_hash: "sha256:eddde134568a3cf09036f21af22f716d287f8e960ca5f23d96bd01a32a4f8511" |
||||
|
plan_revision: 2 |
||||
|
--- |
||||
|
|
||||
|
# Checkpoint: 1 |
||||
|
|
||||
|
## Next |
||||
|
Task 2: create V41 full city policy seed and seed validation tests |
||||
|
|
||||
|
## Completed |
||||
|
V40 schema, entities, repositories, migration and repository tests |
||||
|
|
||||
|
## Evidence |
||||
|
mvn -Dtest=SalaryMigrationTest,SalaryRepositoryIntegrationTest test: 2/2 passed |
||||
|
|
||||
|
## Review |
||||
|
changes/n05-salary-calc/.superpowers/sdd/reviews/T1.md |
||||
|
|
||||
|
## Risk |
||||
|
V41 full city policy data collection remains a release gate |
||||
|
|
||||
|
## Commit Start |
||||
|
36c0214723a3fdd1714ff7923a4f38de07d7b042 |
||||
|
|
||||
|
## Commit End |
||||
|
837c0632c66e6cb9a00e522944f73045258557d6 |
||||
@ -0,0 +1,9 @@ |
|||||
|
# n05-salary-calc 执行进度 |
||||
|
|
||||
|
## W1-W4:完成 |
||||
|
- W1:V40/V41、359 城分层政策;3/3 测试通过。 |
||||
|
- W2:城市查询和计算引擎;15/15 测试通过。 |
||||
|
- W3:计算保存、幂等、历史、权限和注销清理;6/6 测试通过。 |
||||
|
- W4 Task 8:Salary*Test 24/24,OpenAPI 139 paths / 178 operations,文档完成。 |
||||
|
- W4 Task 9:一次性 Docker MySQL 8.4 黑盒通过;Flyway 40/41、359 条城市、12 CITY、347 PROVINCE_FALLBACK,六接口流程通过;容器和网络已销毁。 |
||||
|
- 全量 `mvn clean verify`:1516 tests,2 failures / 9 errors,均为既有非薪资模块问题。 |
||||
@ -0,0 +1,8 @@ |
|||||
|
# Task 1 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: V40 schema, salary city policy/history entities, repositories, migration and persistence tests. |
||||
|
- Evidence: `mvn -Dtest=SalaryMigrationTest,SalaryRepositoryIntegrationTest test` -> 2 tests, 0 failures, 0 errors. |
||||
|
- Contract check: table names, required columns, history owner isolation, idempotency-key query, city ordering, and FK/index obligations are implemented. |
||||
|
- Blockers: none. |
||||
|
- Commit: 837c0632c66e6cb9a00e522944f73045258557d6 |
||||
@ -0,0 +1,8 @@ |
|||||
|
# Task 2 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: V41 layered city policy seed and seed validation. |
||||
|
- Evidence: `mvn -Dtest=SalaryCityPolicySeedTest,SalaryMigrationTest,SalaryRepositoryIntegrationTest test` -> 3 tests, 0 failures, 0 errors. |
||||
|
- Contract check: 359 target city rows; 12 CITY; 347 PROVINCE_FALLBACK; municipality and province-direct city coverage; unique six-digit codes; valid ranges, rates, dates, versions and enabled flags. |
||||
|
- Known limitation: fallback rows use province-level baselines and are explicitly marked. They are not represented as city-level verified values. |
||||
|
- Blockers: none. |
||||
@ -0,0 +1,7 @@ |
|||||
|
# Task 3 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: city list/detail service and controller, STUDENT authorization, data-level response. |
||||
|
- Evidence: `mvn -Dtest=SalaryCityPolicyServiceTest,SalaryCityPolicyControllerTest test` -> 9 tests, 0 failures, 0 errors. |
||||
|
- Contract check: all enabled cities returned; unknown/disabled city 9801; COMPANY 403; anonymous 401; detail includes city defaults and dataLevel. |
||||
|
- Blockers: none. |
||||
@ -0,0 +1,7 @@ |
|||||
|
# Task 4 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: BigDecimal salary calculation engine and input/override/result models. |
||||
|
- Evidence: `mvn -Dtest=SalaryCalculationEngineTest test` -> 6 tests, 0 failures, 0 errors. |
||||
|
- Contract check: base floor/ceiling, user overrides, contribution rates, special deduction, cumulative month 12, prior withheld tax, annual tax brackets, zero contribution, and 9802 validation. |
||||
|
- Blockers: none. |
||||
@ -0,0 +1,7 @@ |
|||||
|
# Task 5 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: calculate-and-save API, policy version validation, normalized request fingerprint and idempotent replay. |
||||
|
- Evidence: `SalaryCalculationEndToEndIntegrationTest` covers success, replay, same-key conflict, stale policy and invalid override. W3 suite 6/6 passed. |
||||
|
- Contract check: no history on version conflict; replay returns same history; structured 9805 data includes latest version/date. |
||||
|
- Blockers: none. |
||||
@ -0,0 +1,7 @@ |
|||||
|
# Task 6 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: history list/detail/physical delete, pagination, user isolation and snapshot JSON. |
||||
|
- Evidence: `SalaryCalculationEndToEndIntegrationTest` covers list, detail, cross-user 404 and physical delete; W3 suite 6/6 passed. |
||||
|
- Contract check: list sorted by repository query; details return input/policy/result snapshots; deleted rows are absent. |
||||
|
- Blockers: none. |
||||
@ -0,0 +1,7 @@ |
|||||
|
# Task 7 self-review |
||||
|
|
||||
|
- Verdict: pass |
||||
|
- Scope: STUDENT authorization, privacy boundaries and account-deletion purge. |
||||
|
- Evidence: `SalaryAuthorizationIntegrationTest` and `SalaryAccountDeletionIntegrationTest`; W3 suite 6/6 passed. |
||||
|
- Privacy check: no direct logger or System.out/printStackTrace use in `com.mydreamjob.salarycalculator`; response DTOs omit requestHash, idempotencyKey and studentUserId. |
||||
|
- Blockers: none. |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue