# 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 通过只能证明现有数据与当前快照一致,不能证明上述两类篡改必然失败。