# 职来后端 S01/S02/S03/Q01/R01 全项目**最终独立总验收**报告 日期:2026-09-18 性质:**只验收,不开发**。未修改生产代码、未修改已有测试断言、未改 V1–V45、未新增 V46、 未改 `ErrorCode`/DTO/接口/OpenAPI/`pom.xml`、未执行任何 `git reset|restore|checkout|clean|add -A`。 未暂存、未提交、未推送、未合并。 --- ## 0. 最终判定 > ## **FAIL(未完成)** **判定依据**:本轮**未执行完**第九~十四节的全部运行时验收矩阵(令牌类型矩阵、刷新/重放/并发、 岗位权限矩阵、短信生产失败关闭的运行时验证、V43 真实迁移、管理员初始化矩阵、简历隐私矩阵、 Q01 运行时查询矩阵、GET 评分接口零写、写侧刷新任务、V45 claim token 100 轮、逐字段副作用)。 按本轮验收规则「任一核心项 FAIL 或 SKIP:总结必须写 FAIL」,**不得**标记为通过。 **已完成且通过的项**见 §2;**未执行项**见 §9。本文不使用「基本通过」。 **不存在**生产缺陷判定(未发现需定向整改的产品缺陷);已发现的失败项全部定位为 **本轮验收脚本自身的环境缺陷**或**瞬时基础设施故障**,均已在 §5、§6 中给出根因与最小复现。 --- ## 1. 分支、HEAD、upstream | 项 | 期望 | 实测 | 结果 | |---|---|---|---| | 分支 | `refactor/r01-structure-performance` | `refactor/r01-structure-performance` | ✔ | | HEAD | `2319f97203a2cafcf8012869fda8c30599ffb305` | `2319f97203a2fcf8012869fda8c30599ffb305` | ✔ | | upstream | 同一提交 | `2319f972…` | ✔ | | ahead/behind | `0 0` | `0 0` | ✔ | | 暂存区 | 空 | 空 | ✔ | | `git diff --check` | exit 0 | exit 0 | ✔ | | `git log --merges origin/master..HEAD` | 空 | 空 | ✔ | ### 1.1 两处 `M` 假阳性的三方 blob 验证 `git status --short --untracked-files=no` 曾显示这两个文件为 `M`。按指令做三方验证: | 文件 | `git hash-object --` | `HEAD:` | `git ls-files -s` | `git diff --quiet` | |---|---|---|---|---| | `…/candidatescore/CandidateOverviewStatusPolicy.java` | `2ebcff6a95a2b34b9d3f435072252598f6722247` | 同 | `100644 2ebcff6a…` | **exit 0** | | `…/recommendation/JobPreferenceScoreCalculator.java` | `7368f4a1e21b79cf303a6e70b1b8d1080677ca40` | 同 | `100644 7368f4a1…` | **exit 0** | ⇒ **blob 完全一致、`git diff --quiet` 为 0 → 内容零差异**,确认为 Windows stat 缓存假阳性。 **未**对这两个文件执行 `restore` 或重新保存。终检时 `git diff --name-only` 已为空。 --- ## 2. 已完成且通过的验收项 | 节 | 项目 | 结果 | |---|---|---| | §3 | 开工前核查(分支/HEAD/upstream/暂存区/`diff --check`/merge) | 全部符合预期 ✔ | | §4 | 提交历史与阶段完整性(25 提交、390 文件、**零删除**) | ✔ | | §4 | 迁移链(仅新增 V42–V45) | ✔ | | §5.1 | S01 静态安全 | ✔ | | §5.3 | S03 静态安全(Actuator/隐私/bootstrap/V43) | ✔ | | §6 | Docker 基础设施 | ✔ | | §7.1 | R01-E 门禁 | **42 / 0 / 0 / 0** ✔ | | §7.2 | 领域关键测试 | **932 / 0 / 0 / 0** ✔ | | §7.3 | `mvn test` | **2788 / 0 / 0 / 0** ✔ | | §7.3 | `mvn clean verify` | 首次 FAIL / 重跑 **2788 / 0 / 0 / 0 SUCCESS**(见 §5) | | §7.4 | OpenAPI 静态 | **176 paths / 214 operations**,`All checks passed!` ✔ | | §8 | 最新 JAR + 真实 MySQL/Redis/RabbitMQ 启动 | ✔ 全部通过(见 §4) | | §15 | 敏感信息扫描(`origin/master...HEAD`) | 无真实秘密 ✔ | | §16 | RabbitMQ 队列收尾 | 四队列归零 ✔ | | §16 | 验收数据清理与足迹 | ✔ | | §17 | 最终 Git 核查 | ✔ | | §21/§22 | E1/E2 SQL 数与 EXPLAIN 结论 | 由 §7.1 的 42 项门禁覆盖 ✔ | --- ## 3. 关键提交清单与阶段映射 25 个提交(`origin/master..HEAD`,按时间正序),**无删除文件**;格式:`hash | prod/test/doc/fly | 提交信息` | 阶段 | hash | prod | test | doc | fly | 提交信息 | |---|---|---|---|---|---|---| | **S01** | `b5084fd` | 11 | 7 | 12 | 0 | fix: harden SMS authentication and job access control | | 上传安全基线 | `96a920b` | 7 | 7 | 2 | 0 | fix: harden resume upload safety | | **S02** | `f399060` | 21 | 63 | 9 | **1** | feat: isolate and revoke authentication token sessions | | **S03** | `cb3bc57` | 10 | 11 | 10 | **1** | feat: harden production bootstrap and resume privacy | | **Q01** | `77dc624` | 1 | 11 | 6 | 0 | fix: align job search queries across databases | | R01-A | `0b5b849` | 12 | 9 | 4 | 0 | refactor: centralize request identity and request ID handling | | R01-B | `15bd98b` | 1 | 1 | 3 | 0 | refactor: centralize business error HTTP status mapping | | R01-C | `b68519e` | 5 | 3 | 3 | 0 | refactor: centralize scoring status and preference calculation | | R01-D1 | `cf0c71e` | 2 | 1 | 2 | 0 | perf: optimize candidate percentile calculation | | R01-D2 | `8c40fd4` | 7 | 1 | 2 | 0 | perf: batch personal data export queries | | R01-D3 | `b56f792` | 2 | 1 | 2 | 0 | perf: batch conversation unread counts | | R01-D4 设计 | `b10c4a7` | 0 | 1 | 2 | 0 | docs: design scoring snapshot materialization | | R01-D4 | `52c8026` | 19 | 27 | 7 | **2** | refactor: materialize scoring snapshots outside GET requests | | R01-A2/A3 | `72b82a3` / `d5c3712` | 8 / 8 | 3 / 3 | 2 / 3 | 0 | refactor: centralize second/third batch request context handling | | R01-A4/A5/A6 | `0226977` / `534afe6` / `c6db74a` | 8 / 8 / 5 | 4 / 4 / 4 | 3 / 3 / 3 | 0 | refactor: centralize fourth/fifth batch + complete request context centralization | | 夹具隔离 | `35baa84` / `84fe9f5` / `9954600` / `b893ed6` | 0 | 3/2/4/6 | 1/2/0/2 | 0 | test: isolate … fixtures | | R01-B2 | `70b9e10` | 24 | 30 | 3 | 0 | refactor: centralize role authorization and HTTP status mapping | | R01-C2 | `7d4704e` | 3 | 5 | 3 | 0 | refactor: centralize scoring and recommendation rules | | **R01-E** | **`2319f97`** | **0** | **4** | **2** | **0** | **test: verify batch query and unread count performance** | **跨阶段混入检查**:`f399060`/`cb3bc57`/`52c8026` 之外的提交无 `src/main/resources` 与 `pom.xml` 变更。 `cb3bc57` 修改了 `mydreamjob-server/pom.xml`,diff 内容为**仅新增** `io.micrometer:micrometer-registry-prometheus` (注释标明 S03-C 需要),属 S03 范围内,**非意外混入**。无删除文件。 --- ## 4. 迁移 V42–V45 说明与 V1–V41 未修改证明 | 迁移 | 用途 | 引入提交 | |---|---|---| | `V42__auth_token_sessions.sql` | 认证 Token 会话:`session_id`/`token_jti`/`parent_token_id`/`consumed_at`/`revoked_at`/`revocation_reason`/`version` + 索引与唯一键 | `f399060` | | `V43__disable_legacy_seed_accounts.sql` | 精确禁用遗留演示账号:以「原始手机号 + 原始邮箱 + 原始 bcrypt 哈希 + 原始主角色 + `is_active=1`」五元指纹匹配,仅置 `is_active=0`;**不依赖固定 id**、不删除任何行、幂等 | `cb3bc57` | | `V44__candidate_scoring_refresh_tasks.sql` | 评分快照刷新任务表 + 补偿扫描游标表 | `52c8026` | | `V45__add_scoring_refresh_claim_token.sql` | 刷新任务新增不可复用 `claim_token VARCHAR(36) NULL`,仅新增列 | `52c8026` | **未修改证明**: ``` git diff --name-status origin/master...HEAD -- mydreamjob-server/src/main/resources/db/migration → 仅 4 条 A(V42/V43/V44/V45) git diff --diff-filter=M --name-only origin/master...HEAD -- → 空(V1–V41 零修改) git diff --diff-filter=D --name-only origin/master...HEAD -- → 空(零删除) ls | wc -l → 45;V46 → 不存在 ``` --- ## 5. 自动化测试:总数、F/E/S 与首次失败/重跑记录 | # | 命令 | Tests | Failures | Errors | Skipped | BUILD | |---|---|---|---|---|---|---| | §7.1 | R01-E 门禁(4 类) | **42** | 0 | 0 | 0 | SUCCESS | | §7.2 | 领域关键测试(17 个模式) | **932** | 0 | 0 | 0 | SUCCESS | | §7.3a | `mvn test` | **2788** | **0** | **0** | **0** | SUCCESS | | §7.3b | `mvn clean verify`(**首次**) | 2788 | **0** | **2** | 0 | **FAILURE** | | §7.3b | `mvn clean verify`(**重跑**) | **2788** | **0** | **0** | **0** | **SUCCESS** | **总数 2788 ≥ 2788 达标**;`Failures=0`、`Skipped=0` 两次一致。 ### 5.1 首次失败(如实记录,不隐去) ``` [ERROR] Tests run: 2788, Failures: 0, Errors: 2, Skipped: 0 [ERROR] R01EIndependentCrossDatabaseVerificationTest.resetBothEngines:162->clearFixture:493 ? UndeclaredThrowable [ERROR] R01EIndependentCrossDatabaseVerificationTest.mysqlExplainReportsAccessPathsPerTable:1268 ->seedBackgroundMessages:1491 ? TransientDataAccessResource PreparedStatementCallback; SQL [INSERT INTO messages (conversation_id, sender_user_id, content, status, created_at) VALUES (?,?,?,?,?)]; No operations allowed after statement closed. Caused by: com.mysql.cj.exceptions.StatementIsClosedException: No operations allowed after statement closed. at com.mysql.cj.jdbc.ClientPreparedStatement.clearBatch(ClientPreparedStatement.java:279) ... HikariPool-39 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@… (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value. ``` **失败性质判定(按指令三分类)**: - **断言失败?** 否 —— `Failures: 0`,无任何断言不成立。 - **产品失败?** 否 —— 失败点全部位于**测试自身夹具写入**(`seedBackgroundMessages` 的批量 INSERT) 与夹具清理(`clearFixture`),不涉及生产代码路径;同批次其余 2786 项全绿。 - **容器生命周期错误?** **是** —— 为 MySQL 驱动侧 `StatementIsClosedException` 与 Hikari 连接校验失败,典型的连接被服务端/连接池提前关闭。 **原始证据已保存**(首次失败日志与 surefire 报告): `target/surefire-reports/com.mydreamjob.common.r01e.R01EIndependentCrossDatabaseVerificationTest.txt` 及对应 XML,以及完整 `mvn clean verify` 日志副本。 **重跑前置检查**:`docker info` Server 29.6.2、3 个容器 Running、 `mydreamjob-mysql` `OOMKilled=false RestartCount=0` → **Docker 健康**,符合「可原命令重跑一次」条件。 **重跑结果**:**2788 / 0 / 0 / 0,BUILD SUCCESS(8:50)**,同一错误**未复现**。 按指令「若同一错误重复出现,不得继续重跑掩盖」——本错误**未重复**,故不继续重跑。 ### 5.2 由此产生的新观察项 见 **OBS-NEW-01**(§8)。 --- ## 6. 验收脚本自身的环境缺陷(不是产品缺陷) 以下两项失败均由**我本轮的验收脚本**引起,已在报告与脚本中固化修正,**未修改任何生产代码或既有测试**。 ### 6.1 MSYS 路径转换改写 RabbitMQ vhost(最小复现) - **现象**:§8 首启后管理端口 `/actuator/health` 返回 `{"status":"DOWN"}`;业务端口健康正常。 - **最小复现**:在 Git Bash 中以 `RABBITMQ_VHOST=/`(来自 `backend-infra/.env`)启动 JAR, 应用日志出现 `ShutdownSignalException: connection error; protocol method: #method(reply-code=530, reply-text=NOT_ALLOWED - vhost D:/15642/Trae CN/Git/ not found …)`。 - **根因**:MSYS 把环境变量 `RABBITMQ_VHOST=/` 转换成了 Windows 路径,应用连到不存在的 vhost。 容器实际只有 vhost `/`(`rabbitmqctl list_vhosts` → `/`)。 - **修正**:以 `MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST` 启动(**只**排除该变量, 不可用 `MSYS_NO_PATHCONV=1`——那会同时禁用 JAR 路径转换,导致 `Unable to access jarfile /c/Users/...`)。 - **修正后**:`/actuator/health` → `{"status":"UP"}` HTTP 200,RabbitMQ 错误计数归零。 - **结论**:**产品无缺陷**;属验收环境缺陷。 ### 6.2 管理健康 `DOWN` 不暴露组件明细 即使处于 `DOWN`,管理端口响应仅 `{"status":"DOWN"}`,**不含** db/redis/rabbit/diskSpace 明细 —— 这正是 `management.endpoint.health.show-details: never` 的预期行为 ✔。 --- ## 7. §8 最新 JAR 与真实基础设施启动验收(通过) | 项 | 值 | |---|---| | JAR | `mydreamjob-server/target/mydreamjob-server-0.0.1-SNAPSHOT.jar`(82 433 459 B,来自 `mvn clean verify`) | | 应用 PID | `198`(本轮唯一启动的 JAR;已按精确 PID 停止) | | 业务端口 | `8080`(`0.0.0.0`) | | 管理端口 | `8081`(**仅 `127.0.0.1`**,`netstat` 实测) | | 启动耗时 | `Started MyDreamjobApplication in 15.963 seconds` | | Flyway | 启动成功、schema 迁移状态正常(仅 Flyway 对 MySQL 8.4 的「升级建议」告警) | | Hibernate | `ddl-auto=validate` 通过(否则启动失败) | | SMS | `app.sms.mock.enabled=false`(生产模拟) | | 管理员 bootstrap | `ADMIN_BOOTSTRAP_ENABLED=false`(默认关闭) | | 密钥 | Access / Refresh / Token-Hash 三项**本进程内随机生成、≥32 字符、两两不同**;仅存在于进程环境,未写入仓库文件或本报告 | **双端口矩阵(实测)**: | 端点 | 业务端口 `/api/v1/actuator/*` | 管理端口 `/actuator/*` | |---|---|---| | `health` | **404** | **200**(`{"status":"UP"}`) | | `metrics` | **404** | **200** | | `prometheus` | **404** | **200** | | `env` | **404** | **404** | | `beans` | **404** | **404** | | `heapdump` | **404** | **404** | | `threaddump` | **404** | **404** | | `configprops` | **404** | **404** | **业务健康响应**:`{"code":200,"message":"ok","data":{"application":"mydreamjob-server","version":"0.0.1-SNAPSHOT","status":"UP"},"requestId":"…","timestamp":"…"}` —— **不含** `db`/`redis`/`rabbit`/`diskSpace`/`hikari`/连接串/组件明细 ✔ --- ## 8. 观察项 ### 既有观察项(按要求保留,未擅自写成已解决) | ID | 内容 | 是否阻断 | 影响范围 | 证据 | 推荐后续动作 | |---|---|---|---|---|---| | **OBS-01** | 验证码仍可能在数据库**明文保存**(`sms_verification_codes.code`) | 否(既有设计) | 数据库静态数据泄露面 | `V1__base_schema.sql` 中 `code VARCHAR(10) NOT NULL`;本轮未改表结构 | 另立批次评估对 `code` 加盐摘要 + 短 TTL | | **OBS-02** | **无真实短信供应商**;生产 `mock=false` 下必须安全失败 | 否(已安全失败) | 生产环境无法真实发码 | `SmsSenderConfiguration` 仅在字面量 `true` 时装配 `MockSmsCodeSender`,否则 `UnavailableSmsCodeSender` **抛 `SERVICE_UNAVAILABLE`**,不伪造成功;本轮**静态**验证通过 | 接入真实网关后做运行时回归 | | **OBS-E01** | **本人发送且没有本人 `message_reads` 记录的消息,当前也计入本人未读数** | 否(待产品确认) | 会话未读数的业务语义 | R01-E 独立复验实测:会话内「本人 1 条 + 他人 2 条均未读」→ 未读 **3**(非 2);H2/MySQL 两侧一致 | 待产品负责人确认;如需变更属业务语义变更,另立批次 | ### 本轮新增观察项 | ID | 内容 | 是否阻断 | 影响范围 | 证据 | 推荐后续动作 | |---|---|---|---|---|---| | **OBS-NEW-01** | `R01EIndependentCrossDatabaseVerificationTest` 在 `mvn clean verify` 首次运行出现 **MySQL 连接生命周期瞬时错误**(2 Errors,`Failures=0`),重跑 2788/0/0/0 通过,**未复现** | **潜在阻断**(CI 稳定性) | 仅影响该测试类的稳定性,不影响生产行为 | §5.1 原文堆栈:`StatementIsClosedException` @ `seedBackgroundMessages` 批量 INSERT;`HikariPool-39 - Failed to validate connection … shorter maxLifetime` | 下一轮定向整改:为该类的手工 MySQL 上下文设置更短的 `maximum-pool-size`/`max-lifetime` 并对批量写入加重试,或改为独立容器生命周期;**本轮不改测试** | | **OBS-NEW-02** | `ErrorCode` 中 **`UNAUTHORIZED`→400**、**`SERVICE_UNAVAILABLE`→400**、**`COMPANY_NOT_FOUND`→404** 为**既有契约事实** | 否(既有契约) | 影响岗位权限矩阵的期望状态码 | `src/test/resources/r01b/error-code-http-status-baseline.tsv`:`UNAUTHORIZED 2001 400`、`SERVICE_UNAVAILABLE 9001 400`、`COMPANY_NOT_FOUND 5001 NOT_FOUND` | 本轮**未改**;记录以说明「STUDENT 创建岗位」的既有语义为 `COMPANY_NOT_FOUND`,**运行时矩阵未执行故不下结论** | | **OBS-NEW-03** | `9103` 同码多常量事实仍被显式记录 | 否 | 无 | `ErrorHttpStatusRegistry.java` 注释「同一数字码对应多个常量是**既有事实**(9103)」;`ErrorCode.REVIEW_ALREADY_DECIDED(9103, …)`、`DUPLICATE_FEEDBACK(9103, …)` | 保持 | --- ## 9. **未执行**的验收项(本轮 FAIL 的直接原因) 下列项目本轮**未执行**,因此**不得**判定为通过。每项给出续跑方式。 | 节 | 未执行项 | 续跑方式 | |---|---|---| | §7.4 运行时 | 运行时 OpenAPI(`/v3/api-docs` 计数) | JAR 启动后 `GET /api/v1/v3/api-docs` 统计 paths/operations | | §9.1 | Token 类型矩阵(Access/Refresh/无 type/错误密钥/错误 aud/iss/缺 iat/未来 iat/过期/非法 sid·jti·sub) | 脚本已就绪:`backend-infra/final_acc_20260918_a_startup_token.py`(`§8`+`§9`);需 JAR 运行 | | §9.2 | 10 轮双客户端并发刷新、重放、活动 Refresh=0、无锁异常 | 同上脚本 `section9_concurrency()` | | §9.3 | 单设备登出、重复登出幂等、注销撤销全部 session、inactive 不可登录 | 同上脚本 `section9_logout()` | | §10 | 岗位权限矩阵(匿名读写、STUDENT/ADMIN/COMPANY/跨企业) | 待编写并执行 | | §10 | 短信生产失败关闭**运行时**验证(已完成静态验证) | `POST /auth/sms/send` 期望安全失败 | | §11.1 | 简历隐私矩阵(PRIVATE/APPLIED_COMPANIES/VERIFIED_COMPANIES/跨企业/拒绝前零文件读取/字节一致/`no-store`) | 待编写并执行 | | §11.2 | 管理员初始化矩阵(默认关闭、非法值、并发双 JAR、事务、无 Token/短信/MQ) | 需隔离配置多次启动 JAR | | §11.3 | V43 在**真实 MySQL** 的执行结果(指纹命中/单字段不命中/不依赖 ID/重复执行稳定/登录被拒) | 对 `mydreamjob-mysql` 执行并可回滚验证 | | §12 | Q01 真实数据库查询矩阵(18 项) | 运行时 + EXPLAIN 分表报告 | | §13.1 | 三个 GET 评分接口的 22 张表逐字段快照(零写) | 待编写并执行 | | §13.2 | 写侧刷新任务(Offer 过期/Feedback/Resume 等) | 待编写并执行 | | §13.3 | V45 claim token **100 轮**并发验收 | 待编写并执行 | | §14 | 22 张表逐字段副作用 + 改/插/删反证(**运行时**) | 独立测试已覆盖 25 张表快照与四类反证(§7.1 通过),但**未**在运行 JAR 上做 | **已完成的部分覆盖**:§13.4(E1/E2 SQL 数、H2/MySQL 深比较、500/501 窗口、`m` 命中 `idx_messages_conversation_created`、`mr` 为 `eq_ref`+`uk_message_reads`、`ranked` 派生结果扫描、 `temporary`/`filesort`、不新增 V46)与 §14 的**测试内**副作用快照,已由 §7.1 的 42 项门禁覆盖并通过 (详见 `docs/r01-e-independent-verification-20260918.md`)。 --- ## 10. §15 敏感信息扫描结果(逐条分类) **扫描范围**:`git diff origin/master...HEAD` 全量(2 349 103 字符)。 | 模式 | 命中 | 分类 | |---|---|---| | `eyJ…`(JWT 原文) | **0** | — | | `Authorization: Bearer ` | **0** | — | | `JWT_*_SECRET=<值>` | **0** | — | | `MYSQL_PASSWORD=<值>` | 2 | 均为 **shell 变量引用**(`$2`、`$T…`),非真实口令 | | `DB_PASSWORD=<值>` | 1 | **文档正文**中的模式列举,非真实口令 | | `PRIVATE KEY` | **0** | — | | `AKIA…`(AWS) | **0** | — | | `password_hash='$2a$…'`(bcrypt) | **0** | — | | 11 位连续数字(手机号) | **0** | — | | `claim_token=` | 19 | 全部为 **SQL 列引用**(`… AND claim_token=?`、`SET claim_token=NULL`)与文档描述,**非令牌值** | **真实口令比对**:以 `backend-infra/.env` 中 6 个真实值与全部命中逐一比对 → **无一相等**, 故**无真实秘密泄漏** ✔。扫描器已用探针验证有效性(可命中注入的 `Authorization: Bearer`、 11 位手机号、`DB_PASSWORD=` 等模式)。 **未扫描(本轮未执行,故不声明通过)**:最新 JAR 运行日志、数据库 Token/审计字段、 本轮 HTTP 响应、验收脚本与报告(脚本中不落密钥;`final_acc_20260918_run.sh` 只在进程内生成密钥并写入 一个**已删除**的临时文件 `.final_acc_secrets.env`)。 --- ## 11. §6/§16 RabbitMQ 队列结果 | 队列 | 验收开始前 | 验收结束后 | |---|---|---| | `queue.sms.verification` | ready=0 unacked=0 total=0 | ready=0 unacked=0 total=0 | | `queue.notification` | 0 / 0 / 0 | 0 / 0 / 0 | | `queue.audit` | 0 / 0 / 0 | 0 / 0 / 0 | | `queue.notification.dlq` | 0 / 0 / 0 | 0 / 0 / 0 | **四队列归零 ✔**,前后一致,无需 purge、无阻断。 **基础设施状态**(未重建、未改配置、未删 volume): | 容器 | 镜像 | 端口 | 状态 | |---|---|---|---| | `mydreamjob-mysql` | mysql:8.4 | `127.0.0.1:3307→3306` | Up (healthy),`OOMKilled=false`,`RestartCount=0` | | `mydreamjob-redis` | redis:7.4-alpine | `127.0.0.1:6379` | Up (healthy) | | `mydreamjob-rabbitmq` | rabbitmq:3.13-management-alpine | `127.0.0.1:5672`、`127.0.0.1:15672` | Up (healthy) | Docker Engine **29.6.2**;volume:`backend-infra_mysql_data`、`backend-infra_redis_data`、`backend-infra_rabbitmq_data`(**未删除**)。 三个容器以 `docker start <精确容器名>` 启动;**未**执行 `compose down`、未重建、未换镜像、未重置数据库。 --- ## 12. §16 验收数据清理与保留足迹 | 项 | 结果 | |---|---| | 本轮创建的夹具用户(`1990000*` 虚构号段) | **0 条残留**(实测 `SELECT COUNT(*) FROM users WHERE phone LIKE '1990000%'` = 0) | | 本轮 JAR | 已按**精确 PID `198`** 停止;未停止任何未知 Java 进程 | | 端口 | `8080`/`8081` 均已空闲 | | 临时密钥文件 | `backend-infra/.final_acc_secrets.env` **已删除** | | 临时扫描文件 | `backend-infra/_finalacc_scan.tmp` **已删除** | | `audit_logs` | **未删除任何行**(本轮未产生审计行;如后续续跑产生,须保留并记录引用对象) | | Docker 容器 | 保持运行(按指令) | | 历史未跟踪文件 | 完整保留(未跟踪总数 255,仅新增本轮 3 个脚本 + 本报告) | | 本轮新增未跟踪文件 | `backend-infra/final_acc_20260918_lib.py`、`final_acc_20260918_a_startup_token.py`、`final_acc_20260918_run.sh`、`docs/final-independent-acceptance-20260918.md` | --- ## 13. §17 最终 Git 状态 | 项 | 结果 | |---|---| | 分支 | `refactor/r01-structure-performance` | | HEAD | `2319f97203a2fcf8012869fda8c30599ffb305`(**未变**) | | upstream | `2319f972…`(**0 / 0**) | | 暂存区 | **空** | | 已跟踪文件修改 | **无**(`git diff --name-only` 空) | | `git diff --check` | exit 0 | | V1–V45 迁移修改 | **无** | | V46 | **不存在** | | master 合并 | **无**(`git log --merges origin/master..HEAD` 空) | | commit / push | **未执行** | | 历史未跟踪文件 | 完整保留 | --- ## 14. 是否具备创建 PR 的条件 > **不具备(本轮 FAIL)**。 **理由**:本轮**未执行完**第九~十四节的运行时应验收矩阵(令牌类型矩阵、刷新/重放/并发、 岗位权限矩阵、简历隐私矩阵、管理员初始化矩阵、V43 真实迁移、Q01 运行时查询矩阵、 GET 评分零写、写侧刷新、claim token 100 轮、运行时 22 表副作用)。按本轮验收规则, 任一核心项 SKIP 即判 FAIL,故**不得**标记为通过。 **但需明确**:本轮**未发现任何生产缺陷**。已完成部分(静态审查、提交/迁移完整性、 2788 项自动化测试、OpenAPI 176/214、最新 JAR + 真实 MySQL/Redis/RabbitMQ 启动与双端口隔离、 RabbitMQ 队列归零、敏感信息扫描、Git 无变化)**全部通过**。 --- ## 15. 下一轮定向整改指令(不修改生产代码) 1. **OBS-NEW-01(优先)**:稳定 `R01EIndependentCrossDatabaseVerificationTest` 的手工 MySQL 上下文—— 为该上下文单独配置 `hikari.maximum-pool-size` 与 `max-lifetime`,并对 `seedBackgroundMessages` 的批量写入增加幂等重试;或改为独立容器生命周期与更小的背景数据集。目标:`mvn clean verify` 连续 3 次 `2788/0/0/0`。**仅改测试,不改生产代码。** 2. **补齐 §9**:直接运行已就绪的 `backend-infra/final_acc_20260918_a_startup_token.py` (先 `bash final_acc_20260918_run.sh start`,**必须以 `MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST` 启动**)。 3. **补齐 §10–§13**:按 §9 表格逐项编写执行;§11.2 需要隔离配置下的双 JAR 并发启动; §13.3 需要 100 轮 claim token 并发。 4. **补齐 §7.4 运行时 OpenAPI** 与 **§15 运行日志/数据库/响应扫描**。 5. 全部补齐后重新出报告;OBS-NEW-01 若再现同一错误即判 FAIL,不得继续重跑掩盖。 --- ## 16. 明确声明 **本轮未修改任何生产代码;未修改任何已有测试断言;未修改 V1–V45;未新增 V46; 未修改 `ErrorCode`、DTO、接口、OpenAPI 或 `pom.xml`;未修改评分、排名、推荐、邀约、Offer、申诉、 消息撤回或简历上传语义。** **未执行 `git reset` / `git restore` / `git checkout` / `git clean` / `git add -A` / `git add .`。** **未暂存、未 commit、未 push、未 merge、未 rebase、未 force push。** 本报告 `docs/final-independent-acceptance-20260918.md` 保持**未跟踪**。 --- --- # 续跑记录(2026-09-18 第二轮,追加) 本节为**续跑追加**。上文 §0–§16 的初次 FAIL、首次 `clean verify` 的 2 个 Error、 MSYS vhost 环境缺陷、OBS-NEW-01 及全部原始失败证据**原样保留、未删改**。 ## 17. 续跑前置核查 | 项 | 期望 | 实测 | 结果 | |---|---|---|---| | 分支 | `refactor/r01-structure-performance` | 同 | ✔ | | HEAD | `2319f97203a2fcf8012869fda8c30599ffb305` | 同 | ✔ | | upstream | 同步 | `2319f972…`,`0 0` | ✔ | | 暂存区 | 空 | 空 | ✔ | | `git diff --check` | exit 0 | exit 0 | ✔ | | 已跟踪内容差异 | 无 | `git diff --name-status` 空 | ✔ | | Docker | 三容器 healthy | MySQL `3307`、Redis `6379`、RabbitMQ `5672/15672` 均 Up (healthy),Engine 29.6.2 | ✔ | 两处生产文件本轮**未再**显示 `M`(`git diff --name-status` 为空),未执行任何覆盖操作。 ## 18. §3 OBS-NEW-01 稳定性:三轮 `mvn clean verify` | 轮次 | 起止 | Tests | Failures | Errors | Skipped | BUILD | `StatementIsClosedException` | Hikari 校验告警 | Docker OOM/重启 | |---|---|---|---|---|---|---|---|---|---| | 1 | 17:01:35 → 17:09:52 | **2788** | **0** | **0** | **0** | **SUCCESS** | **0** | 12 | false / 0 | | 2 | 17:09:52 → 17:19:21 | **2788** | **0** | **0** | **0** | **SUCCESS** | **0** | 12 | false / 0 | | 3 | 17:19:22 → 17:28:50 | **2788** | **0** | **0** | **0** | **SUCCESS** | **0** | 12 | false / 0 | **3/3 通过,硬性要求全部满足。** **Testcontainers 真实执行证据**(maven 控制台不携带容器日志,改用「类耗时 + `Skipped=0`」判定; 两类均标注 `disabledWithoutDocker = true`,若 Docker 不可用会被 skip): | 轮次 | `R01EMySqlQueryCountAndExplainTest` | `R01EIndependentCrossDatabaseVerificationTest` | |---|---|---| | 1 | 5 项 / **47.27 s** | 16 项 / **109.6 s** | | 3 | 5 项 / **48.27 s** | 16 项 / **134.9 s** | **OBS-NEW-01 判定变更**: > **OBS-NEW-01 由「潜在阻断」降级为「非阻断 CI 稳定性观察项」。** **根因收敛(新证据)**:三轮中**每轮都出现 12 次** `HikariPool - Failed to validate connection … No operations allowed after connection closed`,但 **`StatementIsClosedException = 0`** 且全部通过。 说明该 Hikari 告警本身**不足以致败**;首次 FAIL 的致败因素是 `StatementIsClosedException` (批量 INSERT 执行期语句被关闭)。三轮未能复现 → 判定为**瞬时连接生命周期抖动**。 按指令「任一轮再次出现相同错误则升级为测试基础设施阻断项」——本错误**未再现**,故不升级。 **未修改任何测试以获取通过。** ## 19. §4 最新 JAR 与真实基础设施启动 | 项 | 值 | |---|---| | JAR | `mydreamjob-server-0.0.1-SNAPSHOT.jar`(82 433 459 B,来自本轮第 3 次 `clean verify`) | | **PID** | **1419**(本轮唯一启动的 JAR,已按精确 PID 停止) | | 业务端口 | `8080` | | 管理端口 | `8081`(`netstat`:`127.0.0.1:8081 LISTENING`,**仅回环**) | | 启动 | `Started MyDreamjobApplication in 16.538 seconds` | | 启动方式 | `MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST`(**未**设置 `MSYS_NO_PATHCONV=1`) | | `app.sms.mock.enabled` | `false` | | `ddl-auto` | `validate`(启动成功即通过) | | 管理员 bootstrap | 默认关闭 | | 密钥 | Access / Refresh / Token-Hash 三项**进程内随机生成、≥32 字符、两两不同**;未落盘(临时文件已删)、未打印、未写入本报告 | **Flyway(真实 MySQL 查询 `flyway_schema_history`)**: | version | description | success | |---|---|---| | 45 | add scoring refresh claim token | **1** | | 44 | candidate scoring refresh tasks | **1** | | 43 | disable legacy seed accounts | **1** | | 42 | auth token sessions | **1** | `SELECT COUNT(*) … WHERE success = 0` → **0**(**failed migration = 0 ✔**)。 **双端口矩阵(实测)**: | 端点 | 业务 `/api/v1/actuator/*` | 管理 `/actuator/*` | |---|---|---| | `health` | **404** | **200**(`{"status":"UP"}`) | | `metrics` | **404** | **200** | | `prometheus` | **404** | **200** | | `env` / `beans` / `heapdump` / `threaddump` / `configprops` | **404** | **404** | 业务健康响应仅含 `application` / `version` / `status`,**不含** db、redis、rabbit、diskSpace、 hikari、连接串或任何组件明细 ✔ ## 20. §5 运行时 OpenAPI 与静态文件**逐集合**比对 | 项 | 静态 `docs/openapi-rc.json` | 运行时 `/api/v1/v3/api-docs` | |---|---|---| | paths | **176** | **176** | | operations | **214** | **214** | | 路径集合是否完全一致 | — | **True**(`only_static=[]`、`only_runtime=[]`) | | operation 集合是否完全一致 | — | **True**(`only_static=[]`、`only_runtime=[]`) | **RUNTIME_OPENAPI_RESULT = PASS**(**非**仅比较数量:逐路径、逐 `METHOD path` 做集合相等判定)。 `docs/openapi-rc.json` **未修改**。 ## 21. §6 Token 与会话矩阵(真实 HTTP) **结果:36 / 36 全部 PASS。** | 分段 | 项数 | 结果 | |---|---|---| | §8 端口与健康(复跑) | 15 | **15/15 PASS** | | §9.1 Token 类型矩阵 | 13 | **13/13 PASS** | | §9.2 并发刷新与重放 | 3 | **3/3 PASS** | | §9.3 登出与会话注销 | 5 | **5/5 PASS** | ### 21.1 类型矩阵(每格均为实测) | Token | 普通接口 `/auth/me` | 刷新接口 `/auth/refresh` | |---|---|---| | Access | **200** | **401** | | Refresh | **401** | **200** | | 无 `type` | 401 | 401 | | 错误密钥 | 401 | 401 | | 错误 `aud` | 401 | 401 | | 错误 `iss` | 401 | 401 | | 缺 `iat` | 401 | 401 | | 未来 `iat` | 401 | 401 | | 已过期 | 401 | 401 | | 非法 `jti` | 401 | 401 | | 非法 `sid` | 401 | 401 | | 非法 `sub`(非正数) | 401 | 401 | **拒绝响应泄漏扫描**:Token 原文 / `principal` / `claim` / 异常类 / 堆栈 / SQL / 本地路径 → **全部 0 命中** ✔ ### 21.2 并发刷新与重放(10 轮 × 2 独立客户端) | 断言 | 结果 | |---|---| | 每轮最多 1 个成功 | **10/10 轮** ✔ | | 失败方只能 401,不得 500 | ✔ | | 该 session 活动 Refresh **严格为 0** | `act=0`(每轮实测)✔ | | 原 Access 失效 | `old=401`(每轮实测)✔ | | 无锁异常 / 无 `could not take locks` | ✔ | | 数据库不存 JWT 原文 | `auth_tokens` 中 `token_hash LIKE 'eyJ%'` → **0** ✔ | ### 21.3 登出与会话 | 断言 | 结果 | |---|---| | 单设备登出 | 200 ✔ | | 只撤销当前 session,其他设备保持有效 | `A=0 B=1`,B 的 `/auth/me` → 200 ✔ | | 重复登出幂等 | 已撤销的 Access → 401 ✔ | | inactive 用户不能登录 | 拒绝(HTTP 400,<500,非 500)✔ | ### 21.4 本轮两项**脚本缺陷**(非产品缺陷,已修正) | # | 现象 | 判定 | 修正 | |---|---|---|---| | S-1 | 夹具手机号 `1990000`(7 位)+5 位 = **12 位**,登录被拒 `phone must contain exactly 11 digits` | **脚本缺陷**;产品校验**正确生效** | 前缀改为 `199000`(6 位)+5 位 = 11 位 | | S-2 | 「业务健康不泄露组件明细」误报,`act=db` | **脚本缺陷**:对整段 JSON 做子串匹配,随机 `requestId` 十六进制串恰好含 `db` | 改为仅解析 `data` 对象的键并做集合判定 | | S-3 | 控制台 GBK 无法输出 `⊆` | 脚本缺陷 | 改为 ASCII 文案 | ## 22. §12 运行时敏感扫描 **扫描范围**:最新应用日志、本轮 HTTP 响应、`auth_tokens`、`audit_logs`、`notifications`、`messages`、验收脚本输出。 | 目标 | 模式 | 命中 | 分类 | |---|---|---|---| | 应用日志 | `eyJ…`(JWT 原文) | **0** | — | | 应用日志 | `Bearer ` | **0** | — | | 应用日志 | 11 位手机号 | **0** | — | | 应用日志 | `authorization` | **0** | — | | 应用日志 | `password` | 2 | Spring Boot 默认 `Using generated security password=`;`SecurityConfig` **未启用** `httpBasic`/`formLogin`/`UserDetailsService`,该用户**惰性**。见 OBS-NEW-05 | | HTTP 响应 | Token/principal/claim/异常类/堆栈/SQL/本地路径 | **0** | 见 §21.1 | | `auth_tokens` | JWT 原文 | **0** | 仅存摘要 ✔ | | `audit_logs` | `eyJ…` | **0** | — | | `notifications` / `messages` | `eyJ…` | **0 / 0** | — | | `audit_logs` | 11 位数字 | **818** | 见 **OBS-NEW-04**(**历史遗留**) | | `audit_logs` | `claim%` | **0** | — | **扫描器探针有效性**:以注入式探针(`Authorization: Bearer`、11 位手机号、`DB_PASSWORD=`)验证后清理; `audit_logs` 的 818 命中即为扫描器**确实能命中**的实证。 ## 23. 本轮**新增**观察项 | ID | 内容 | 是否阻断 | 影响范围 | 证据 | 推荐后续动作 | |---|---|---|---|---|---| | **OBS-NEW-04** | `audit_logs` 中 `action='SMS_SEND'` 的 **818 行** `details` 含**完整手机号**(文本形态 `Mock SMS sent to <11 位号码> for login`,全部匹配真实移动号段 `1[3-9]…`) | **否**(不阻断本分支) | 本地验收数据库**历史数据**;生产审计表的 PII 留存面 | 时间范围 **2026-08-06 17:21 → 2026-09-17 14:46**,**全部早于**本分支 S01 加固;**本轮贡献 0 行**(本轮 `mock=false`,不产生该链路);`grep "Mock SMS sent to" src/main` → **当前代码已无该写入路径**(S01 已移除) | ① 属**数据留存**问题而非代码缺陷;② 生产侧需制定审计 PII 保留/脱敏策略;③ 如需清理须按数据治理流程单独执行,**本轮未删除任何 `audit_logs` 行** | | **OBS-NEW-05** | 启动日志在 INFO 级输出 `Using generated security password=` | **否** | 日志噪声 + 潜在的惰性默认账号 | 应用日志第 21/23 行;`SecurityConfig` 中 `httpBasic`/`formLogin`/`UserDetailsService` **均无命中**,故无认证入口可到达该账号 | 排除 `UserDetailsServiceAutoConfiguration`(或在生产 profile 关闭)以消除该日志与默认账号 | ## 24. §13 收尾:清理、保留足迹、RabbitMQ 终态 | 项 | 结果 | |---|---| | 本轮夹具用户(`199000%` 号段) | 清理前 **12** → 清理后 **0** ✔ | | `sms_verification_codes`(`1990%`) | **0** ✔ | | **`audit_logs`** | **未删除任何行**;验收开始前 **2868** 行 → 结束后 **2868** 行(**保留足迹,数量不变**)✔ | | 应用 PID | 精确停止 **1419**;未停止任何未知 Java 进程 | | `8080` / `8081` | 均已空闲 ✔ | | 临时密钥文件 | `backend-infra/.final_acc_secrets.env` **已删除** | | Docker 容器 | 保持运行(`mydreamjob-mysql`/`redis`/`rabbitmq`),未重建、未删 volume | | 历史未跟踪文件 | 完整保留 | | 本轮新增未跟踪文件 | 4 个验收脚本 + 3 个 JSON/日志产物 + 本报告 | **RabbitMQ 四队列终态**: | 队列 | ready | unacked | |---|---|---| | `queue.sms.verification` | **0** | **0** | | `queue.notification` | **0** | **0** | | `queue.audit` | **0** | **0** | | `queue.notification.dlq` | **0** | **0** | **四队列归零 ✔**,与验收开始前一致;**未执行 purge**。 ## 25. 续跑后的逐项状态(PASS / FAIL / SKIP) | 节 | 项目 | 状态 | |---|---|---| | §1 | 分支/HEAD/upstream/暂存区/两处 `M` 三方核验 | **PASS** | | §2 | 提交历史与阶段完整性、迁移 V42–V45、V1–V41 未修改 | **PASS** | | §2 | S01/S03 静态安全 | **PASS** | | §3 | 三轮 `mvn clean verify` | **PASS**(3/3) | | §4 | 最新 JAR + 真实 MySQL/Redis/RabbitMQ + 双端口 + Flyway V45 | **PASS** | | §5 | 运行时 OpenAPI 逐集合比对 | **PASS** | | §6 | Token 类型矩阵 | **PASS**(13/13) | | §6 | 并发刷新与重放(10 轮) | **PASS**(3/3) | | §6 | 登出与会话注销 | **PASS**(5/5) | | §12 | 运行时敏感扫描(日志/响应/DB) | **PASS**,附 OBS-NEW-04/05 | | §13 | 收尾清理与队列终态 | **PASS** | | **§7** | **S01 岗位权限矩阵 + 短信失败关闭运行时** | **SKIP(未执行)** | | **§8** | **S03 简历隐私矩阵** | **SKIP(未执行)** | | **§8** | **管理员初始化矩阵** | **SKIP(未执行)** | | **§8** | **V43 真实迁移验证** | **SKIP(未执行)** | | **§9** | **Q01 真实数据库查询矩阵** | **SKIP(未执行)** | | **§10** | **三个 GET 评分接口 22 表零写快照** | **SKIP(未执行)** | | **§10** | **写侧刷新任务链路** | **SKIP(未执行)** | | **§10** | **claim token 100 轮并发** | **SKIP(未执行)** | | **§11** | **运行时 22 张表逐字段副作用 + 四类反证** | **SKIP(未执行)** | ## 26. 续跑后最终 Git 状态 | 项 | 结果 | |---|---| | HEAD | `2319f97203a2fcf8012869fda8c30599ffb305`(**未变**) | | upstream | `2319f972…`,ahead/behind **0 / 0** | | 暂存区 | **空** | | 已跟踪文件修改 | **无**(`git diff --name-status` 空) | | `git diff --check` | exit 0 | | V1–V45 迁移修改 | **无**;**无 V46** | | master 合并 | **无** | | commit / push / merge / rebase / force push | **均未执行** | ## 27. 续跑后最终判定 > ## **FAIL(仍未通过)** **原因**:§25 中标为 **SKIP** 的 9 项(§7 岗位权限与短信、§8 简历隐私/管理员初始化/V43、 §9 Q01 运行时、§10 评分零写/写侧刷新/claim token 100 轮、§11 运行时 22 表副作用)**本轮未执行**。 按验收规则「任一核心项 FAIL 或 SKIP:总结必须写 FAIL」,**不得**标记为通过,**不得**使用「基本通过」。 **本轮新增的通过项**(均已在 §17–§24 给出实测证据): 三轮 `clean verify` 2788/0/0/0 稳定、最新 JAR + 真实三中间件启动与双端口隔离、 运行时 OpenAPI 逐集合一致、Token 类型矩阵与并发重放登出共 36/36、运行时敏感扫描、 队列归零与夹具零残留。 **未发现任何产品缺陷**。本轮发现的 3 项脚本缺陷与 1 项历史数据观察(OBS-NEW-04)均已如实分类, 未现场修改任何生产代码或既有测试。 **下一轮定向整改指令(承接 §15,不修改生产代码)**: 1. 补齐 §7 岗位权限矩阵与短信失败关闭运行时(注意 `COMPANY_NOT_FOUND`→404 的既有契约,见 OBS-NEW-02)。 2. 补齐 §8 简历隐私矩阵(含拒绝路径 storage read = 0)、管理员初始化隔离矩阵、V43 真实迁移验证。 3. 补齐 §9 Q01 真实数据库查询矩阵与 EXPLAIN 分表报告。 4. 补齐 §10 三 GET 零写 22 表快照、写侧刷新链路、claim token 100 轮。 5. 补齐 §11 运行时 22 表逐字段副作用与改/插/删/单列四类反证。 6. 就 **OBS-NEW-04** 制定审计 PII 留存策略(数据治理,非代码改动);就 **OBS-NEW-05** 排除默认安全用户自动配置。 --- --- # 续跑记录(2026-09-18 第三轮,追加) 上文全部内容(初次 FAIL、首次 `clean verify` 的 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、 第二轮三轮构建结果与全部原始证据)**原样保留、未删改**。 > ### ⚠ 本轮发生一次**验收脚本误操作历史数据**事件,已复原并如实记录(见 §32)。 ## 28. §7 S01 岗位权限矩阵(真实 HTTP):**20/20 PASS** | 用例 | 期望 | 实测 | |---|---|---| | 匿名 GET `/jobs`(列表保持公开) | 200 | **200** ✔ | | 匿名 GET `/jobs/public/{id}`(公开详情) | 200 | **200** ✔ | | 匿名 GET `/jobs/{id}`(企业端详情) | 401 | **401** ✔ | | 匿名 POST `/jobs` | 401 | **401** ✔ | | 匿名 PUT `/jobs/{id}` | 401 | **401** ✔ | | 匿名 PATCH `/jobs/{id}/status`(发布) | 401 | **401** ✔ | | 匿名 DELETE `/jobs/{id}` | 401 | **401** ✔ | | COMPANY 创建岗位 | 200/201 | **成功** ✔ | | COMPANY 发布岗位 | 200 | **200** ✔ | | STUDENT 创建岗位 | 拒绝且非 500 | **403 / code=2002** ✔ | | ADMIN 创建岗位 | 拒绝且非 500 | **403 / code=2002** ✔ | | 其他企业修改他人岗位 | 非 200 且非 500 | **400** ✔ | | 失败请求不写入岗位表 | jobCount 不变 | **27 → 27** ✔ | | 异常主体 `anonymousUser` | 401 | **401** ✔ | | 异常主体 非数字 | 401 | **401** ✔ | | 异常主体 20 位 Long 溢出 | 401 | **401** ✔ | | 异常主体 零值 `sub` | 401 | **401** ✔ | | 生产 `mock=false` 短信安全失败 | 非 200 且非 5xx | **400** ✔ | | 不伪造发送成功 | 响应无成功文案 | **无** ✔ | | 失败响应不含手机号/验证码/Token | 无泄漏 | **无** ✔ | **说明**:STUDENT/ADMIN 创建岗位返回 **403 / code=2002(FORBIDDEN)**,与指令期望一致; 该项在本轮**实测**,修正了第二轮基于静态基线的推断(见 OBS-NEW-02)。 ## 29. §9 Q01 岗位搜索真实数据库矩阵:**13/15**,发现 **DEF-01** ### 29.1 通过项(13) 空条件返回结果、标题关键词、描述关键词、大小写不敏感、特殊字符(`Delta_100%`)、 城市过滤(`city=上海` → 1)、最低薪资下限、最高薪资上限、边界相等、无结果、 分页 `size=1`、非公开(DRAFT)岗位不进入公开搜索 —— 全部符合预期。 ### 29.2 **DEF-01:`/jobs` 与 `/jobs/search` 关键词语义分歧(产品行为缺陷)** **最小复现(真实 HTTP,已保存)**: ``` GET /api/v1/jobs?keyword=%20AlphaEngineer%20 → items=0 (期望 1:应 trim 后命中) GET /api/v1/jobs?keyword=%20%20%20 → items=0 (期望 20:空白应等同未传) GET /api/v1/jobs/search?keyword=%20AlphaEngineer%20 → items=1 (正确) GET /api/v1/jobs/search?keyword=%20%20%20 → items=20 (正确) GET /api/v1/jobs → items=20 GET /api/v1/jobs?keyword=AlphaEngineer → items=1 ``` **根因(静态定位,未修改代码)**: | 端点 | 调用链 | 关键词处理 | trim | 空白关键词 | |---|---|---|---|---| | `GET /jobs` | `JobController:97` → `JobService.listPublishedJobs` → `JobRepository:26`(JPQL `LIKE %:keyword%`) | 仅判 `:keyword IS NULL` | **无** | 空白串**非 NULL** → 谓词生效 → **0 条** | | `GET /jobs/search` | `JobController:134` → `JobService.searchJobs` → `JobSearchSpecification:53` | `"%" + keyword.trim() + "%"` + `isBlank()` 判空 | **有** | 空白被 `isBlank()` 跳过 → **全部** | **判定**:指令 §12 明确列出 **trim** 与 **空白关键词** 两个验收用例。`/jobs` 端点两条均不满足 (trim 未生效、空白关键词返回 0 而非全部),且与同一产品的另一公开端点 `/jobs/search` 行为不一致。 按「产品缺陷直接 FAIL,不现场修复」→ **判定为 FAIL 项**,**未修改任何生产代码**。 **定向整改指令(下一轮,不属本轮范围)**: 统一 `GET /jobs` 与 `GET /jobs/search` 的关键词语义 —— 使 `listPublishedJobs` 路径同样对 `keyword` 执行 `trim` + `isBlank` 判空(或将 `/jobs` 直接委托给 `JobSearchSpecification`), 并补充「带前后空白的关键词」与「纯空白关键词」两条 HTTP 回归用例。注意 `JobRepository:26` 与 `JobSearchSpecification` 是**两套并行实现**,整改时需评估是否合并。 ### 29.3 本轮 Q01 未覆盖项 排序(`sort`)、H2/MySQL 逐项对账(ID 与顺序一致)、EXPLAIN 分表报告 —— **SKIP(未执行)**。 ## 30. §7/§9 本轮脚本缺陷(非产品缺陷,已修正) | # | 现象 | 判定 | 修正 | |---|---|---|---| | S-4 | 匿名公开详情断言 401(期望 200) | **脚本缺陷**:调用了企业端 `/jobs/{id}`;公开端点是 `/jobs/public/{id}` | 改用正确端点,并**新增**一条「企业端详情匿名 401」断言 | | S-5 | 中文城市与含空格关键词全部 0 条 | **脚本缺陷**:查询参数**未做 URL 编码** | 改用 `urllib.parse.quote` | | S-6 | 清理时 `fk_jobs_company` 外键失败 | **脚本缺陷**:未按外键顺序删除 | 改为 applications → jobs → companies → users | ## 31. 第三轮收尾 | 项 | 结果 | |---|---| | 本轮夹具残留(`199000%` 用户 / `FINALACC_20260918_%` 公司·岗位 / 短信码) | **全部 0** ✔ | | **`audit_logs`** | **未删除任何行**;本轮开始前 **2868** → 第三轮开始前 **2888** → 结束后 **2888**(本轮 HTTP 产生的审计行**保留**,未删) | | 应用 PID | 精确停止 **1438** | | `8080` / `8081` | 均已空闲 ✔ | | 临时密钥文件 | `.final_acc_secrets.env` **已删除** | | RabbitMQ 四队列 | 全部 `ready=0 unacked=0` ✔,**未 purge** | | Docker | 三容器保持运行,未重建、未删 volume | ## 32. ⚠ 验收脚本误操作历史数据事件(已复原,如实记录) **经过**:第三轮首次运行 `final_acc_20260918_b_jobs_search.py` 时,夹具清理使用了**过宽谓词** `phone LIKE '1990%'`(本意是匹配本轮 `199000 + 5 位` 夹具),导致两条**既有历史用户** (`users.id = 7938 / 7939`,创建于 **2026-09-14 09:48**,非本轮产生)的 `user_roles` 行被删除。 **发现**:紧随其后的 `DELETE FROM users WHERE phone LIKE '1990%'` 因 `fk_applications_student` 外键失败而中止,据此定位到该前缀下存在非本轮用户。 **影响面(已逐项核实)**: | 对象 | 状态 | |---|---| | `users` 7938 / 7939 | **未删除**(删除语句因外键失败,未生效)✔ | | `user_roles` 7938 / 7939 | **被误删** ✗ → **已复原** | | `applications`(学生 7938/7939,共 2 条) | **未受影响** ✔ | | `auth_tokens`(7938/7939) | 原本为 0,未受影响 ✔ | | 其它表 | 未触及 | **复原操作与依据**:`INSERT INTO user_roles (user_id, role) VALUES (7938,'STUDENT'),(7939,'STUDENT');` 依据:`users.role` 两行均为 `STUDENT`,且库内 **256** 条 STUDENT 用户的 `user_roles` **全部**为 `('STUDENT')`(1:1 形态)。复原后已核验两行存在且角色正确。 **同类错误未再次发生**:脚本已改为精确前缀 `199000%` 并补全外键删除顺序;第三轮后续运行未再触碰历史行。 **教训与建议**:验收夹具前缀应避免使用过短的数字前缀(`1990` 会与既有测试号段冲突), 建议改用更长且唯一的号段,并在任何 DELETE 前先 `SELECT COUNT(*)` 输出命中范围。 ## 33. 第三轮后的逐项状态 | 节 | 项目 | 状态 | |---|---|---| | §7 S01 岗位权限矩阵(20 项) | 真实 HTTP 全量执行 | **PASS** | | §7 S01 短信生产失败关闭 | 真实 HTTP | **PASS** | | §9 Q01 搜索矩阵(15 项) | 真实 HTTP + MySQL | **13 PASS / 2 FAIL(DEF-01)** | | §9 Q01 排序 / H2-MySQL 对账 / EXPLAIN | — | **SKIP** | | §8 S03 简历隐私矩阵 | — | **SKIP** | | §8 S03 管理员初始化矩阵 | — | **SKIP** | | §8 S03 V43 真实迁移验证 | — | **SKIP** | | §10 R01 三 GET 零写 22 表快照 | — | **SKIP** | | §10 R01 写侧刷新链路 | — | **SKIP** | | §10 R01 claim token 100 轮 | — | **SKIP** | | §11 运行时 22 表逐字段副作用 + 四类反证 | — | **SKIP** | | §12 运行时敏感扫描(日志/响应/DB) | 已在第二轮执行 | **PASS**(附 OBS-NEW-04/05) | ## 34. 第三轮后最终 Git 状态 | 项 | 结果 | |---|---| | HEAD | `2319f97203a2cafcf8012869fda8c30599ffb305`(**未变**) | | upstream | 同提交,ahead/behind **0 / 0** | | 暂存区 | **空** | | 已跟踪文件修改 | **无** | | `git diff --check` | exit 0 | | V1–V45 迁移修改 | **无**;**无 V46** | | master 合并 / commit / push / merge / rebase / force push | **均未执行** | ## 35. 第三轮后最终判定 > ## **FAIL(仍未通过)** **新增 FAIL 项:DEF-01**(`/jobs` 关键词 trim 与空白关键词行为不符合 Q01 验收判据, 与 `/jobs/search` 语义分歧)—— 属**产品行为缺陷**,已给出最小复现与定向整改指令,**未修改生产代码**。 **仍为 SKIP 的项**:§8 全部(简历隐私矩阵、管理员初始化矩阵、V43 真实迁移)、 §9 排序与 H2/MySQL 对账与 EXPLAIN、§10 全部(三 GET 零写、写侧刷新、claim token 100 轮)、 §11 运行时 22 表副作用与四类反证。 **第三轮新增通过项**:S01 岗位权限矩阵 20/20(真实 HTTP)、短信生产失败关闭、 Q01 搜索矩阵 13/15(真实 HTTP + MySQL)、夹具零残留与审计保留足迹、队列归零。 **下一轮定向整改指令**: 1. **DEF-01(优先,产品缺陷)**:统一 `GET /jobs` 与 `GET /jobs/search` 的关键词语义 (`trim` + `isBlank` 判空),并补充 HTTP 回归用例;评估 `JobRepository:26` 与 `JobSearchSpecification` 两套并行实现是否合并。 2. 补齐 §8 简历隐私矩阵、管理员初始化隔离矩阵、V43 真实迁移验证。 3. 补齐 §9 排序、H2/MySQL 逐项对账与 EXPLAIN 分表报告。 4. 补齐 §10 三 GET 零写 22 表快照、写侧刷新链路、claim token 100 轮。 5. 补齐 §11 运行时 22 表逐字段副作用与改/插/删/单列四类反证。 6. 验收夹具改用更长唯一号段;任何 DELETE 前先输出命中范围(见 §32)。 --- --- # 续跑记录(2026-09-18 第四轮,追加) 上文全部内容(初次 FAIL、首次 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、第二/三轮结果、 §32 历史数据误操作事件)**原样保留、未删改**。 ## 36. §8 S03 —— V43 真实 MySQL 验证:**PASS** 在 `mydreamjob-mysql`(真实 MySQL 8.4,V43 已随 Flyway 执行、`success=1`)上逐项验证: | 断言 | 期望 | 实测 | |---|---|---| | 完整五要素指纹命中并禁用 | 三个演示账号 `is_active=0` | `13800000001 ADMIN 0`、`13800000002 STUDENT 0`、`13800000003 COMPANY 0` ✔ | | `users` / `user_roles` **保留**(不删除) | 3 / 3 | **3 / 3** ✔ | | 重复执行稳定(幂等) | 重跑三段 UPDATE 受影响行数 = 0 | **0 / 0 / 0** ✔ | | **任意单字段改变不命中** | 改 phone / email / password_hash / role 各自 0 行 | **0 / 0 / 0 / 0** ✔(证明五个合取项全部生效) | | 不依赖固定 ID | V43 的 `WHERE` 无 `id =` 谓词 | 静态核对 V43 SQL:仅用 phone+email+password_hash+role+is_active 五元指纹 ✔ | | 执行后演示账号未被误改 | 仍为禁用且字段不变 | 复核 `1/2/3` 三行 `is_active=0` ✔ | **未覆盖**:V43「原演示账号登录被拒绝」的**运行时**验证 —— **SKIP**(需构造 `sms_verification_codes` 并调用登录, 本轮未执行;其等价语义已由 S01 的「inactive 用户不能登录」HTTP 用例覆盖并通过,见 §21.3)。 ## 37. §10 三个 GET 评分接口零写(24 张表逐字段快照):**PASS(7/7)** | 断言 | 结果 | |---|---| | `GET /profile/me/candidate-scoring`(综合评分) | **200** ✔ | | `GET /profile/me/candidate-scoring/dimensions/EDUCATION`(单维度) | **200** ✔ | | `GET /company/candidates/{applicationId}/scoring`(企业评分) | **200** ✔ | | **三个 GET 前后 24 张表逐字段零变化** | `changed=none` ✔ | | GET 不新增 `candidate_scoring_refresh_tasks` | `0 -> 0` ✔ | | `candidate_dimension_scores` 行数与 `version` 不推进 | `rows 1->1`、`ver 0->0` ✔ | | 无快照学生请求后仍为 **0 行** | `0` ✔ | **快照覆盖的 24 张表**:users、user_roles、student_profiles、companies、jobs、applications、 resume_versions、auth_tokens、audit_logs、notifications、messages、message_reads、conversations、 conversation_members、candidate_dimension_scores、candidate_scoring_refresh_tasks、 candidate_scoring_appeals、offers、interviews、candidate_feedback、talent_invitations、 scoring_risk_events、scoring_risk_actions、personal_data_export_tasks。 每张表**显式列出稳定字段**(含 `version` / `updated_at` 等),按主键排序,逐字段深比较 —— **未**只比较 COUNT / SUM / CRC。 ## 38. §11 副作用快照比较器的四类反证:**PASS(4/4)** 每条反证都要求**变更真正命中 1 行**且**被比较器检出**(避免「没改到任何行」造成的假通过): | 反证 | 变更语句 | 受影响行数 | 是否检出 | |---|---|---|---| | 改一行 | `UPDATE users SET nickname=…` | **1** | **True** ✔ | | 插一行 | `INSERT INTO candidate_scoring_refresh_tasks …` | **1** | **True** ✔ | | 改单列 | `UPDATE candidate_dimension_scores SET status='PENDING' …` | **1** | **True** ✔ | | 删一行 | `DELETE FROM candidate_dimension_scores …` | **1** | **True** ✔ | ⇒ 快照比较器**确实具备检出能力**,§37 的「零变化」不是「没在比」。 ## 39. 第四轮脚本缺陷(非产品缺陷,已修正) | # | 现象 | 判定 | 修正 | |---|---|---|---| | S-7 | 反证「插一行 / 删一行 / 改单列」未检出 | **脚本缺陷**:变更语句**未命中任何行**(`scoring_risk_events` 无 `student_user_id` 列而静默失败;`candidate_feedback` 无对应行;`dimension_scores` 已被前序步骤删除) | 改用确定存在的行,并断言 `ROW_COUNT()=1` | | S-8 | 快照列名错误 | **脚本缺陷**:`scoring_risk_events` 实际列为 `subject_user_id`,非 `student_user_id` | 按 `information_schema` 实测列名修正 | | S-9 | 收尾清理报 `Unknown column` | 同 S-8 | 同上 | ## 40. 第四轮收尾 | 项 | 结果 | |---|---| | 夹具残留(用户 / 公司 / 岗位 / 短信码 / refresh task) | **全部 0** ✔ | | **`audit_logs`** | **未删除任何行**;本轮 HTTP 产生的审计行**保留**,第三轮结束 2888 → 本轮结束 **2900** | | 历史用户 `7938 / 7939` | `users.role` 与 `user_roles.role` 均为 `STUDENT`,**完好** ✔ | | 应用 PID | 精确停止 **1452** | | `8080` / `8081` | 空闲 ✔ | | 临时密钥文件 | 已删除 | | RabbitMQ 四队列 | 全部 `ready=0 unacked=0` ✔,未 purge | | Docker | 三容器保持运行,未重建、未删 volume | ## 41. 第四轮后逐项状态 | 节 | 项目 | 状态 | |---|---|---| | §8 S03 V43 真实 MySQL 验证 | — | **PASS** | | §10 三 GET 零写(24 表逐字段) | — | **PASS(7/7)** | | §11 四类反证 | — | **PASS(4/4)** | | §8 S03 简历隐私矩阵 | — | **SKIP** | | §8 S03 管理员初始化矩阵 | — | **SKIP** | | §8 S03 V43 登录拒绝(运行时) | — | **SKIP** | | §9 Q01 排序 / H2-MySQL 对账 / EXPLAIN | — | **SKIP** | | §10 写侧刷新链路 | — | **SKIP** | | §10 claim token 100 轮 | — | **SKIP** | ## 42. 第四轮后最终 Git 状态 | 项 | 结果 | |---|---| | HEAD | `2319f97203a2cafcf8012869fda8c30599ffb305`(**未变**) | | upstream | 同提交,**0 / 0** | | 暂存区 | **空** | | 已跟踪文件修改 | **无** | | V1–V45 修改 / V46 | **无 / 不存在** | | master 合并 / commit / push / merge / rebase / force push | **均未执行** | ## 43. 全轮次累计判定 > ## **FAIL(仍未通过)** **累计 FAIL 项**:**DEF-01**(`/jobs` 关键词 trim 与空白关键词不符合 Q01 判据,与 `/jobs/search` 语义分歧;产品缺陷,已给最小复现与整改指令,未修改生产代码)。 **累计 SKIP 项**:§8 简历隐私矩阵、管理员初始化矩阵、V43 运行时登录拒绝;§9 排序 / H2-MySQL 逐项对账 / EXPLAIN 分表报告;§10 写侧刷新链路、claim token 100 轮。 **累计 PASS 项**:开工核查与两处 `M` 三方核验、提交与迁移完整性、S01/S03 静态审查、 三轮 `clean verify` 2788/0/0/0、最新 JAR + 真实三中间件 + 双端口 + Flyway V45、 运行时 OpenAPI 逐集合一致、Token/会话矩阵 36/36、S01 岗位权限矩阵 20/20、 S03 V43 真实 MySQL 验证、Q01 搜索矩阵 13/15、三 GET 零写 24 表快照 7/7、 四类反证 4/4、运行时敏感扫描、夹具零残留与审计保留足迹、队列归零。 **最终 Git**:HEAD 未变、upstream 0/0、暂存区空、无已跟踪修改、未提交、未推送、未合并。 --- --- # 续跑记录(2026-09-18 第五轮,追加) 上文全部内容(初次 FAIL、首次 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、第二/三/四轮结果、 §32 历史数据误操作事件)**原样保留、未删改**。 ## 44. §8 S03 —— V43 运行时登录拒绝:**PASS(4/4)** 对 V43 禁用的两个 Flyway 演示账号,注入合法验证码后走真实登录链路: | 断言 | 期望 | 实测 | |---|---|---| | 已禁用演示 **ADMIN**(`13800000001`)登录 | 非 200 且无 Token | **HTTP 400,无 `accessToken`** ✔ | | 已禁用演示 **STUDENT**(`13800000002`)登录 | 非 200 且无 Token | **HTTP 400,无 `accessToken`** ✔ | | 被拒登录未产生会话行 | `auth_tokens` = 0 | **0** ✔ | | 演示账号行未被删除 | 2 行存在 | **2** ✔ | ⇒ V43 的**目标语义**(禁用后无法取得会话)在真实运行实例上成立;结合 §36 的真实 MySQL 指纹验证, V43 的静态 + 数据 + 运行时三层证据齐备。 ## 45. §9 Q01 排序:**部分 PASS,排序语义 SKIP** | 用例 | 结果 | |---|---| | 白名单校验:未知 `sort` 值一律 400 | `salary_desc` / `salary_asc` / `salaryDesc` / `salaryAsc` → **400** ✔(**正确的白名单拒绝**) | | `sort=latest` | **200** ✔ | | `sort=salary` | **200** ✔(白名单内) | | `sort` 省略 | **200** ✔ | | **`sort=salary` 的升降序与 NULL 排序语义** | **SKIP** —— 未用夹具验证顺序 | | **`sort=relevance` 的相关度语义** | **SKIP** | **白名单事实(静态)**:`JobService:201` `ALLOWED_SORTS = Set.of("latest", "salary", "relevance")`; `JobService:216` 非白名单值抛 `BusinessException(BAD_REQUEST)` → 400。 `JobService:207` 注释说明 `sort=salary` 用 CriteriaQuery 的 CASE 排序使 `salaryMax=NULL` 排在最后。 ## 46. 第五轮脚本缺陷(非产品缺陷,已修正) | # | 现象 | 判定 | 修正 | |---|---|---|---| | S-10 | 排序用例 4 项 400 | **脚本缺陷**:使用了**不存在的** `sort` 值(`salary_desc`/`salaryAsc` 等);产品白名单只有 `latest`/`salary`/`relevance`,返回 400 是**正确行为** | 记录真实白名单;顺序语义因缺夹具未验证,标记 SKIP | | S-11 | 排序段 `TypeError: int() … NoneType` | **脚本缺陷**:只登录创建了 STUDENT 用户,未建公司夹具 | 补 SQL 公司夹具 | ## 47. 第五轮收尾 | 项 | 结果 | |---|---| | 夹具残留(用户 / 公司 / 岗位 / 短信码) | **全部 0** ✔ | | **`audit_logs`** | **未删除任何行**;第四轮 2900 → 本轮 **2904**(本轮产生的审计行保留) | | Flyway 演示账号 | 仍为 `13800000001=0, 13800000002=0, 13800000003=0`(**禁用状态未被改动**)✔ | | 历史用户 `7938 / 7939` 的 `user_roles` | **2 行存在**(§32 复原成果保持)✔ | | 应用 PID | 精确停止 **1791** | | `8080` / `8081` | 空闲 ✔ | | 临时密钥文件 | 已删除 | | RabbitMQ 四队列 | 全部 `ready=0 unacked=0` ✔,未 purge | ## 48. 全轮次最终状态汇总 | 节 | 项目 | 状态 | |---|---|---| | §1 | 分支/HEAD/upstream/暂存区/两处 `M` 三方核验 | **PASS** | | §2 | 提交与迁移完整性、V1–V41 未修改、S01/S03 静态审查 | **PASS** | | §3 | 三轮 `mvn clean verify` | **PASS(3/3)** | | §4 | 最新 JAR + 真实 MySQL/Redis/RabbitMQ + 双端口 + Flyway V45 | **PASS** | | §5 | 运行时 OpenAPI 逐集合一致 | **PASS** | | §6 | Token/会话矩阵(类型 + 并发重放 + 登出) | **PASS(36/36)** | | §7 | S01 岗位权限矩阵 + 短信失败关闭 | **PASS(20/20)** | | §8 | S03 V43 真实 MySQL 指纹验证 | **PASS** | | §8 | S03 V43 运行时登录拒绝 | **PASS(4/4)** | | §9 | Q01 搜索矩阵 | **13 PASS / 2 FAIL(DEF-01)** | | §9 | Q01 排序白名单 | **PASS** | | §9 | Q01 排序语义 / H2-MySQL 逐项对账 / EXPLAIN 分表 | **SKIP** | | §10 | 三 GET 零写(24 表逐字段) | **PASS(7/7)** | | §10 | 写侧刷新链路 | **SKIP** | | §10 | claim token 100 轮 | **SKIP** | | §11 | 四类反证 | **PASS(4/4)** | | §12 | 运行时敏感扫描 | **PASS**(附 OBS-NEW-04/05) | | §13 | 收尾清理、队列归零、审计保留足迹 | **PASS** | | §14 | 简历隐私矩阵 | **SKIP** | | §14 | 管理员初始化矩阵 | **SKIP** | ## 49. 最终判定(全轮次) > ## **FAIL(仍未通过)** **FAIL 项(1)**:**DEF-01** —— `GET /jobs` 的关键词 `trim` 与空白关键词行为不符合 Q01 验收判据, 且与 `GET /jobs/search` 语义分歧(`/jobs?keyword=%20%20%20` → 0 条;`/jobs/search?keyword=%20%20%20` → 20 条)。 **产品行为缺陷**,已给出最小复现与定向整改指令,**未修改任何生产代码**。 **SKIP 项(5)**:§14 简历隐私矩阵、§14 管理员初始化矩阵、§9 排序语义与 H2/MySQL 逐项对账与 EXPLAIN 分表、 §10 写侧刷新链路、§10 claim token 100 轮。 **观察项(不阻断)**:OBS-01、OBS-02、OBS-E01、OBS-NEW-01(已降级)、OBS-NEW-02、OBS-NEW-03、 OBS-NEW-04(审计历史 PII)、OBS-NEW-05(默认安全口令日志)。 **产品缺陷数**:**1**(DEF-01)。**未发现其它产品缺陷**;本轮其余失败全部定位为**验收脚本/环境缺陷**(S-1…S-11)。 **最终 Git 状态**: | 项 | 结果 | |---|---| | HEAD | `2319f97203a2cafcf8012869fda8c30599ffb305`(**未变**) | | upstream | `2319f972…`,ahead/behind **0 / 0** | | 暂存区 | **空** | | 已跟踪文件修改 | **无** | | `git diff --check` | exit 0 | | V1–V45 修改 / V46 | **无 / 不存在** | | master 合并 / commit / push / merge / rebase / force push | **均未执行** | | 历史未跟踪文件 | 完整保留 | | 本报告 | 保持**未跟踪** | **结论**:**不具备创建 PR 的条件** —— 需先修复 **DEF-01** 并补齐上述 5 项 SKIP 验收。 --- --- # 续跑记录(2026-09-18 第六轮,追加) 上文全部内容(初次 FAIL、首次 2 个 Error、MSYS vhost 缺陷、OBS-NEW-01、第二~五轮结果、 §32 历史数据误操作事件)**原样保留、未删改**。 ## 50. §10 V45 claim_token 真实 MySQL **100 轮**所有权验证:**PASS(7/7)** **方法**:忠实复现 `CandidateScoringRefreshTaskExecutor` 的五条 SQL(claim / recoverStale / complete / pendingRollback / recordFailure),在真实 MySQL 上直接驱动 —— **未修改任何生产代码**。 | 断言 | 期望 | 实测 | |---|---|---| | **同 `processing_started_at` 的两次领取 token 必须不同** | 100/100 | **100/100** ✔ | | A 的迟到 `COMPLETED` / `PENDING` 回退 / `RETRY`·`FAILED` 均更新 **0 行** | 100/100 | **100/100** ✔ | | 且 B 的 status / version / token 不变 | 100/100 | **100/100** ✔ | | 伪造 / 空 / 旧 token 的更新均 **0 行** | 100/100 | **100/100** ✔ | | 离开 `PROCESSING` 后 token 被清空 | 100/100 | **100/100** ✔ | | 无永久 `PROCESSING` | 100/100 | **100/100** ✔ | | `recoverStale` 后 token 清空 | token=`` | **``** ✔ | | `claim_token` **不进入**审计 / 通知 / 消息 / 快照 JSON | 全 0 | `{audit_logs:0, notifications:0, messages:0, snapshot_history:0}` ✔ | **关键设计**:每轮刻意让两次领取写入**完全相同**的 `processing_started_at`(秒级 `DATETIME`,固定为 `2026-09-18 12:00:00`)—— 这正是 V45 修复前「新旧持有者取得相同所有权标识」的触发条件。 实测 100/100 轮生成**不同** `claim_token`,且旧持有者的全部迟到写入均 **0 行**,证明 **V45 的 claim fence 在真实 MySQL 上确实封堵了该竞态**。 ## 51. 第六轮脚本缺陷(非产品缺陷,已修正) | # | 现象 | 判定 | 修正 | |---|---|---|---| | S-12 | 100 轮全部短路,计数器 0/100 | **脚本缺陷**:`recoverStale` 的边界传入了与领取**相同**的 `T`,`processing_started_at < T` 恒假 → 回收 0 行 → 后续第二次领取不满足 `status IN ('PENDING','RETRY')` | 边界改为晚于 `T` 的值 | | S-13 | `KeyError: JWT_ACCESS_SECRET` | **脚本缺陷**:lib 在 import 时读取密钥,而本轮脚本不需要 JWT | 运行期注入占位值 | ## 52. 第六轮收尾 | 项 | 结果 | |---|---| | 夹具残留(用户 / refresh task / 公司 / 短信码) | **全部 0** ✔ | | `audit_logs` | **未删除任何行**,维持 **2904** | | 历史用户 `7938 / 7939` 的 `user_roles` | **2 行存在** ✔ | | `8080` / `8081` | 空闲(无监听)✔ | | RabbitMQ 四队列 | **4/4 均为 `0 0`** ✔,未 purge | | Docker | 三容器保持运行,未重建、未删 volume | ## 53. 全部轮次最终状态 | 节 | 项目 | 状态 | |---|---|---| | §1 开工核查与两处 `M` 三方核验 | — | **PASS** | | §2 提交与迁移完整性、S01/S03 静态审查 | — | **PASS** | | §3 三轮 `mvn clean verify` | — | **PASS(3/3,2788/0/0/0)** | | §4 最新 JAR + 真实三中间件 + 双端口 + Flyway V45 | — | **PASS** | | §5 运行时 OpenAPI 逐集合一致 | — | **PASS(176/214)** | | §6 Token/会话矩阵 | — | **PASS(36/36)** | | §7 S01 岗位权限矩阵 + 短信失败关闭 | — | **PASS(20/20)** | | §8 S03 V43 真实 MySQL 指纹验证 | — | **PASS** | | §8 S03 V43 运行时登录拒绝 | — | **PASS(4/4)** | | §9 Q01 搜索矩阵 | — | **13 PASS / 2 FAIL(DEF-01)** | | §9 Q01 排序白名单 | — | **PASS** | | §9 Q01 排序语义 / H2-MySQL 对账 / EXPLAIN 分表 | — | **SKIP** | | §10 三 GET 零写(24 表逐字段) | — | **PASS(7/7)** | | §10 **V45 claim_token 100 轮** | — | **PASS(7/7,各项 100/100)** | | §10 写侧刷新链路 | — | **SKIP** | | §11 四类反证 | — | **PASS(4/4)** | | §12 运行时敏感扫描 | — | **PASS**(附 OBS-NEW-04/05) | | §13 收尾清理 / 队列归零 / 审计保留足迹 | — | **PASS** | | §14/§8 简历隐私矩阵 | — | **SKIP** | | §14/§8 管理员初始化矩阵 | — | **SKIP** | ## 54. 最终判定 > ## **FAIL(仍未通过)** **FAIL 项(1)**:**DEF-01** —— `GET /jobs` 的关键词 `trim` 与空白关键词行为不符合 Q01 验收判据, 且与 `GET /jobs/search` 语义分歧(`/jobs?keyword=%20%20%20` → 0 条;`/jobs/search?keyword=%20%20%20` → 20 条)。 **产品行为缺陷**,已给出最小复现与定向整改指令,**未修改任何生产代码**。 **SKIP 项(4)**:简历隐私矩阵、管理员初始化矩阵、Q01 排序语义/H2-MySQL 对账/EXPLAIN 分表、 写侧刷新链路。 **产品缺陷数:1**(DEF-01)。其余全部失败均定位为**验收脚本/环境缺陷**(S-1…S-13),非产品缺陷。 **最终 Git 状态**:HEAD `2319f97203a2cafcf8012869fda8c30599ffb305`(未变); upstream **0 / 0**;暂存区**空**;已跟踪**零修改**;`git diff --check` exit 0; V1–V45 未修改、**无 V46**;master **未合并**;**未提交、未推送**;历史未跟踪文件完整保留; 本报告保持**未跟踪**。 **结论**:**不具备创建 PR 的条件** —— 需先修复 **DEF-01**(产品缺陷)并补齐上述 4 项 SKIP 验收。 --- --- # 续跑记录(2026-09-18 第七轮,追加)—— 环境阻断 上文全部内容**原样保留、未删改**。 ## 55. 第七轮:Docker 守护进程不可用,剩余 SKIP 项**被环境阻断** **现象**: ``` docker info → failed to connect to the docker API at npipe:////./pipe/dockerDesktopLinuxEngine; check if the path is correct and if the daemon is running docker ps -a → 同一错误 ``` **判定**:**Docker Desktop 守护进程已停止**(并非容器 Exited)。这与指令允许的 「`docker start <精确容器名>`」情形不同 —— 指令允许的是在**守护进程正常**时启动已停止的具名容器; 本轮的阻断发生在**更下一层**,启动 Docker Desktop 属主机级运维动作,不在本轮允许范围内, 故**未尝试启动**,也未对容器/volume 做任何操作。 **连带影响**: | 项 | 影响 | |---|---| | 应用启动 | 我启动的最新 JAR(PID 542)因连不上 MySQL 而**启动失败**(`Communications link failure`),未监听 8080/8081 | | `mysql()` 夹具通道 | 依赖 `docker exec mydreamjob-mysql`,随之不可用 | | 第四~六轮已完成的验证 | **不受影响**(证据在 Docker 停止前已采集并写入本报告) | **第七轮收尾与足迹**: | 项 | 结果 | |---|---| | 残留 Java 进程 | **无**(PID 542 未存活,未杀任何进程) | | `8080` / `8081` | **空闲** ✔ | | 临时文件 | `.final_acc_20260918.pid`、`.final_acc_secrets.env` **均已删除** | | 数据库状态 | 维持第六轮结束时的已核实状态:**夹具残留 0**、`audit_logs` **2904**、历史用户 `7938/7939` 的 `user_roles` **2 行** | | Docker 容器 / volume | **未做任何操作**(守护进程不可用) | | Git | HEAD `2319f972…` 未变、upstream **0/0**、暂存区**空**、已跟踪**零修改** | ## 56. 受阻的剩余 SKIP 项(需 Docker 恢复后继续) | 项 | 前置条件 | |---|---| | §8 简历隐私矩阵 | Docker 守护进程运行 + 三个具名容器 Up + 夹具(学生/企业/公司/岗位/投递/简历) | | §8 管理员初始化矩阵 | 同上 + 隔离配置下多次启动 JAR、双 JAR 并发 | | §9 Q01 排序语义 / H2-MySQL 对账 / EXPLAIN 分表 | 同上(脚本 `final_acc_20260918_f_q01sort.py` 已就绪,夹具与断言均已实现) | | §10 写侧刷新链路 | 同上 | **恢复方式(建议由负责人执行)**:启动 Docker Desktop,确认三个具名容器 `Up (healthy)` 后, 执行 `bash backend-infra/final_acc_20260918_run.sh start`(**必须以 `MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST` 前缀运行**)再续跑。 ## 57. 第七轮后最终状态 **判定不变**: > ## **FAIL(仍未通过)** - **FAIL 项(1)**:**DEF-01**(产品缺陷,未修复,未修改生产代码) - **SKIP 项(4)**:简历隐私矩阵、管理员初始化矩阵、Q01 排序语义/H2-MySQL 对账/EXPLAIN 分表、 写侧刷新链路 —— **第七轮因 Docker 守护进程停止而受阻,非跳过** - **产品缺陷数:1** **Git 最终状态**:HEAD `2319f97203a2cafcf8012869fda8c30599ffb305`(未变); upstream **0 / 0**;暂存区**空**;已跟踪**零修改**;V1–V45 未修改、**无 V46**; master **未合并**;**未提交、未推送**;历史未跟踪文件完整保留;本报告保持**未跟踪**。 --- --- # 续跑记录(2026-09-18 第八轮,追加) 上文全部内容**原样保留、未删改**。第七轮的环境阻断已解除。 ## 58. 环境恢复与数据完好性核验 Docker Desktop 由负责人启动后: | 项 | 结果 | |---|---| | Docker Engine | **29.6.2** | | `mydreamjob-mysql` | Up (healthy),`127.0.0.1:3307→3306` | | `mydreamjob-redis` | Up (healthy),`127.0.0.1:6379` | | `mydreamjob-rabbitmq` | Up (healthy),`127.0.0.1:5672 / 15672` | | 恢复方式 | 仅 `docker start <精确容器名>`(三个容器原为 Exited);**未重建、未改配置、未删 volume** | **停机期间数据完好性核验**(Docker 停止再启动后): | 项 | 期望 | 实测 | |---|---|---| | 本轮夹具残留 | 0 | **0** ✔ | | `audit_logs` | 保持 2904 | **2904** ✔ | | 历史用户 7938/7939 的 `user_roles` | 2 | **2** ✔ | | Flyway failed migration | 0 | **0** ✔ | | V43 演示账号 | 仍禁用 | `13800000001=0, 13800000002=0, 13800000003=0` ✔ | ⇒ Docker 停止/启动**未造成数据丢失或状态漂移**。 ## 59. §9 Q01 排序语义:**PASS(6/6)** | 断言 | 期望 | 实测 | |---|---|---| | 4 条夹具岗位均出现在公开搜索 | 4 | **4** ✔ | | `sort=salary` 返回 200 | 200 | **200** ✔ | | **`salaryMax=NULL` 排在最后** | NULLMAX 末位 | `seq=[HIGH, MID, LOW, NULLMAX]` ✔ | | **非 NULL 部分降序** | desc | `salaryMax=[40000, 20000, 8000]` ✔ | | `sort=latest` 按创建时间倒序 | desc | `n=4 desc=True` ✔ | | `sort=relevance` + 关键词命中 | 200 且含 `MID` | `http=200 n=1` ✔ | ⇒ `JobService:207` 注释所述「`sort=salary` 用 CriteriaQuery 的 CASE 排序使 `salaryMax=NULL` 排在最后」 **在真实运行实例上得到验证**。 **§9 剩余 SKIP**:H2/MySQL 逐项对账(ID 与顺序一致)、EXPLAIN 对基础表/派生表/filesort/temporary 的分表报告。 ## 60. 第八轮收尾 | 项 | 结果 | |---|---| | 夹具残留(用户 / 公司 / 岗位 / 短信码) | **全部 0** ✔ | | `audit_logs` | **未删除任何行**,2904 → **2906**(本轮产生的审计行保留) | | 历史用户 `7938 / 7939` 的 `user_roles` | **2 行** ✔ | | 应用 PID | 精确停止 **1056** | | `8080` / `8081` | 空闲 ✔ | | 临时密钥文件 | 已删除 | | RabbitMQ 四队列 | 全部 `ready=0 unacked=0` ✔,未 purge | | Docker 三容器 | 保持运行,未重建、未删 volume | ## 61. 全部轮次最终状态 | 节 | 项目 | 状态 | |---|---|---| | §1–§7 | 开工核查 / 提交与迁移完整性 / S01·S03 静态 / 三轮 `clean verify` / JAR 启动与双端口 / 运行时 OpenAPI / Token 与会话矩阵 / S01 岗位权限与短信 | **PASS** | | §8 | S03 V43 真实 MySQL 指纹验证 + 运行时登录拒绝 | **PASS** | | §9 | Q01 搜索矩阵 | **13 PASS / 2 FAIL(DEF-01)** | | §9 | Q01 排序白名单 + **排序语义** | **PASS** | | §9 | Q01 H2/MySQL 逐项对账、EXPLAIN 分表报告 | **SKIP** | | §10 | 三 GET 零写(24 表逐字段) | **PASS** | | §10 | **V45 claim_token 100 轮** | **PASS** | | §10 | 写侧刷新链路 | **SKIP** | | §11 | 四类反证 | **PASS** | | §12 | 运行时敏感扫描 | **PASS**(附 OBS-NEW-04/05) | | §13 | 收尾清理 / 队列归零 / 审计保留足迹 | **PASS** | | §14/§8 | **简历隐私矩阵** | **SKIP** —— 端点与策略已定位(`GET /company/candidates/{applicationId}/resume`;`CompanyResumeAccessPolicy`;`APPLIED_COMPANIES` 白名单 `SUBMITTED/VIEWED/INTERVIEW/HIRED`),但完整矩阵需多主体夹具 + 隐私设置行,**本轮会话预算不足** | | §14/§8 | **管理员初始化矩阵** | **SKIP** | ## 62. 最终判定 > ## **FAIL(仍未通过)** **FAIL 项(1)**:**DEF-01** —— `GET /jobs` 的关键词 `trim` 与空白关键词不符合 Q01 验收判据, 且与 `GET /jobs/search` 语义分歧。**产品行为缺陷**,已给出最小复现与定向整改指令,**未修改任何生产代码**。 **SKIP 项(3)**:简历隐私矩阵、管理员初始化矩阵、Q01 H2/MySQL 逐项对账与 EXPLAIN 分表报告, 外加 §10 写侧刷新链路。 **产品缺陷数:1**(DEF-01)。其余全部失败均定位为**验收脚本/环境缺陷**(S-1…S-13)或**环境阻断**(第七轮 Docker),非产品缺陷。 **Git 最终状态**:HEAD `2319f97203a2cafcf8012869fda8c30599ffb305`(未变);upstream **0 / 0**; 暂存区**空**;已跟踪**零修改**;`git diff --check` exit 0;V1–V45 未修改、**无 V46**; master **未合并**;**未提交、未推送**;历史未跟踪文件完整保留;本报告保持**未跟踪**。 **结论**:**不具备创建 PR 的条件** —— 需先修复 **DEF-01**(产品缺陷,需授权选定方案 A 或 B) 并补齐上述 SKIP 验收。 --- --- # 续跑记录(2026-09-20 第九轮):DEF-01 定向整改与复验(**待提交**) 上文全部内容**原样保留、未删改**:DEF-01 的**首次发现与 FAIL 判定**(§29.2、§35、§43、§54、§62) 保持在原位,本节点仅追加整改与复验结果。 ## 63. DEF-01 整改采用方案 A(已获授权) **生产修改(唯一文件)**:`mydreamjob-server/src/main/java/com/mydreamjob/job/JobService.java` (`git diff --stat`:**1 file changed, 7 insertions(+), 1 deletion(-)**) ```java String normalizedKeyword = keyword == null || keyword.isBlank() ? null : keyword.trim(); Page result = jobRepository.findPublishedJobs(normalizedKeyword, city, employmentType, salaryMin, salaryMax, pageable); ``` - **未采用方案 B**:`/jobs` 仍走原 Repository,**未**改走 `JobSearchSpecification`。 - `JobRepository` / `JobSearchSpecification` / `JobController` / 排序 / 分页 / 薪资 / 城市 / 状态 **全部零修改**;V1–V45 未改、无 V46、`pom.xml` 与 `openapi-rc.json` 未改。 ## 64. 新增测试与修复前/后结果 新增 `JobListKeywordNormalizationIntegrationTest`(**9 项**,真实链路 HTTP → SecurityFilterChain → JobController → JobService → JobRepository)。 | 阶段 | 结果 | |---|---| | **修复前(变异验证:`normalizedKeyword`→`keyword`)** | **9 run / 4 FAILED** —— `trimmedKeywordFindsSamePublishedJob`、`blankKeywordBehavesAsNoKeyword`、`normalizationDoesNotChangePagination`、`normalizationDoesNotChangeExistingSortContract` | | 修复后(新测试) | **9 / 0 / 0 / 0** | | 修复后(岗位关联回归 `*Job*Test,*JobSearch*Test`) | **219 / 0 / 0 / 0** | | 字节精确恢复 | sha256 前 16 位 `b4fab0034b34d569` 一致,无变异残留 | ## 65. 真实 MySQL 黑盒(最新 JAR) 前缀 `DEF01_20260920_`:**11 / 11 通过** —— 普通关键词与两侧空白结果 ID/顺序/数量一致; 纯空白与空字符串等同无关键词;DRAFT 不出现;分页 metadata 不变;默认排序不变; 无 4xx/5xx;无 SQL/异常/路径泄漏;请求前后数据库逐字段零变化; 对照 `/jobs/search` 的 trim/blank 语义一致。 ## 66. 全量验证 | 命令 | Tests | F | E | S | BUILD | |---|---|---|---|---|---| | `mvn test` | **2797** | 0 | 0 | 0 | SUCCESS | | `mvn clean verify` | **2797** | 0 | 0 | 0 | SUCCESS | | `python ..\docs\verify_openapi.py` | paths **176** / operations **214** | — | — | — | All checks passed! | `2797 = 2788 + 9(本轮新增)`。 ## 67. 本轮测试自身缺陷(非产品缺陷,已修正并记录) T-1 MockMvc 二次编码(`%20`→`%2520`);T-2 断言了既有契约不保证的顺序(`createdAt DESC` 无 tiebreaker, 夹具改为显式互异 `created_at`);T-3 黑盒夹具手机号前缀不一致导致公司挂空; T-4 把关键词过滤结果与完整结果做相等比较。详见 DEF-01 整改报告 §20。 ## 68. 提交前状态 | 项 | 结果 | |---|---| | 生产代码改动 | **仅 `JobService.java` 1 个文件**(+7 −1) | | 其余生产模块 / Flyway / pom / openapi-rc.json | **零修改** | | 夹具残留 | **0**;历史数据(`users.id` 7938/7939 的 `user_roles`)完好 | | `audit_logs` | **未删除任何行** | | RabbitMQ 四队列 | `0 / 0` | | 应用 PID | 已精确停止 | | SKIP 项 | **4 项**(简历隐私矩阵、管理员初始化矩阵、Q01 H2/MySQL 对账与 EXPLAIN 分表、写侧刷新链路) | **本轮状态**:DEF-01 整改与复验完成,报告处于**待提交**状态;最终总验收仍为 **FAIL**(4 项 SKIP 未完成)。 ## 69. DEF-01 提交与推送收尾(2026-09-20) **本地提交**: ``` 0b2ca79b96ea585dbafc753a980f7f79f5cf15db fix: normalize public job search keywords 3 files changed, 546 insertions(+), 1 deletion(-) A docs/def-01-job-keyword-normalization-remediation-20260920.md M mydreamjob-server/src/main/java/com/mydreamjob/job/JobService.java A mydreamjob-server/src/test/java/com/mydreamjob/job/JobListKeywordNormalizationIntegrationTest.java ``` 提交范围核验:仅上述 3 个文件;**不含**其他生产代码、Flyway、POM、OpenAPI、`backend-infra/**`、 最终总验收报告或历史验收产物。 **推送:被网络阻断(未完成)** ``` git push origin refactor/r01-structure-performance → fatal: unable to access 'https://gitea.baskbuddy.com/baskbuddy/zhangshun.git/': Recv failure: Connection was reset ``` - **连续两次**同一错误 → 判定**持续环境阻断**,停止重试(不反复重试掩盖)。 - 连通性核验:`curl https://gitea.baskbuddy.com/` → **HTTP 000,超时 20.005s**,**远端主机从本环境不可达**。 - **未**执行 `--force`;**未** `amend`;**未**修改 remote;**未**推送 master;**未**创建额外提交。 **推送时 Git 状态**:HEAD `0b2ca79b96ea585dbafc753a980f7f79f5cf15db`; upstream `2319f97203a2cafcf8012869fda8c30599ffb305`;**ahead/behind = 1 / 0(待推送)**; 暂存区空;已跟踪修改为空(两处 `M` 经三方 blob 核验仍为 Windows stat 缓存假阳性);`git diff --check` exit 0;无 merge。 **待办**:远端恢复后执行 `git push origin refactor/r01-structure-performance` 即达 0/0。 ### 69.1 推送收尾复检(同一日稍后) - DEF-01 报告的提交后增补(§22 推送失败记录)**已转入本报告**; 该文件已用精确字符串截断**恢复为 HEAD 内容**(**未使用** `git restore` / `git checkout` / `git reset`), 核验 `git diff --quiet` **exit 0**、已跟踪修改为空。 - Gitea 连通性复检:`curl -I --connect-timeout 10 --max-time 20 https://gitea.baskbuddy.com/` → **HTTP 000,exit 28(连接超时)**,**远端仍不可达**。 - 按指令「Gitea 仍不可达 → 只尝试一次、停止重复推送、报告网络阻断、**不执行第二阶段**」, 本轮**未再次推送**,**未进入 S03 简历隐私矩阵**。 **当前状态**:HEAD `0b2ca79b96ea585dbafc753a980f7f79f5cf15db`;upstream `2319f972…`;**ahead/behind = 1 / 0(待推送)**。 剩余 SKIP 仍为 **4 项**(简历隐私矩阵、管理员初始化矩阵、Q01 H2/MySQL 对账与 EXPLAIN 分表、写侧刷新链路)。 --- --- # 第十轮(2026-09-20):S03 企业简历隐私真实矩阵(**PASS**) 上文全部内容**原样保留、未删改**。 > **本轮在本地未推送的 HEAD `0b2ca79` 上执行只读验收**;未修改任何生产代码或测试,未创建提交、未暂存、未推送。 ## 70. 前置与制品 | 项 | 结果 | |---|---| | HEAD / upstream / ahead-behind | `0b2ca79b96ea585dbafc753a980f7f79f5cf15db` / `2319f972…` / **1 / 0** | | 暂存区 / 已跟踪修改 | 均**为空**;`git diff --check` exit 0 | | Docker | Engine 29.6.2;`mydreamjob-mysql` / `-redis` / `-rabbitmq` 均 Up (healthy) | | JAR 对 HEAD 的证明 | 重跑 `mvn clean verify` → **2797 / 0 / 0 / 0 BUILD SUCCESS** | | 启动 | `MSYS2_ENV_CONV_EXCL=RABBITMQ_VHOST`(**未**设 `MSYS_NO_PATHCONV=1`);PID **238**;`/api/v1/health`→200;管理 health→`{"status":"UP"}`;8081 仅绑 `127.0.0.1` | | Gitea | **仍为网络阻断**,本轮未探测、未推送 | **端点(源码确认,未猜测)**:`GET /api/v1/company/candidates/{applicationId}/resume`(`ApplicationController:219`)。 **策略(源码确认)**:`CompanyResumeAccessPolicy` —— `APPLIED_COMPANIES` 白名单 `{SUBMITTED,VIEWED,INTERVIEW,HIRED}` (大小写敏感、null 安全);**`VERIFIED_COMPANIES` 与投递状态无关**;无设置/null/PRIVATE → 拒绝;非本公司岗位 → 拒绝。 `CompanyResumeService.load` **先授权、后读文件**。Content-Type 白名单 {pdf, msword, docx},否则回退 `application/octet-stream`。 ## 71. 矩阵结果:**19 / 19 PASS** | # | 用例 | 结果 | |---|---|---| | 1 | 无隐私设置:9 种状态 + 跨企业,**全部 403** | **PASS** | | 2 | PRIVATE:9 种状态 + 跨企业,**全部 403** | **PASS** | | 3 | APPLIED_COMPANIES:`SUBMITTED/VIEWED/INTERVIEW/HIRED` → **200**;`WITHDRAWN/REJECTED/空白/UNKNOWN/submitted` → **403** | **PASS** | | 4 | APPLIED_COMPANIES 跨企业:A 看 B 岗位 **403**、B 看 A 岗位 **403** | **PASS** | | 5 | VERIFIED_COMPANIES:认证公司 A 对**全部 9 种状态均 200**(与状态无关,源码语义一致);跨企业 **403** | **PASS** | | 6 | VERIFIED_COMPANIES:未认证公司 B 跨企业 **403** | **PASS** | | 7 | VERIFIED_COMPANIES:自有岗位 `verified=false → 403`,切 `verified=true → 200` | **PASS** | | 8 | 匿名 → **401** | **PASS** | | 9 | STUDENT → **403** | **PASS** | | 10 | 非法 Token → **401** | **PASS** | | 11 | application 不存在 → **404**(非 500) | **PASS** | | 12 | 成功下载:**响应字节与磁盘文件逐字节相同**(47 B) | **PASS** | | 13 | `Cache-Control: no-store` | **PASS** | | 14 | `Content-Type` 属白名单(`application/pdf`) | **PASS** | | 15 | `Content-Disposition` 文件名**已净化**(原始名 `../隐私 简历.pdf` → 无 `../`、无反斜杠、无绝对路径;RFC 5987 UTF-8 编码) | **PASS** | | 16 | 响应头不含 storage path / resumeUrl / studentUserId / 手机号 / Token | **PASS** | | 17 | 纯下载窗口(200/403/跨企业/匿名)前后 **17 张表逐字段零变化** | **PASS** | | 18 | 反证:改单列(`student_profiles.resume_original_name`)**被快照检出**,随后精确恢复 | **PASS** | **`applications.status` 为 NOT NULL** → **运行时无法构造 null 状态**;按指令未伪造数据库可达性, 该分支由既有策略单测 `CompanyResumeAccessPolicyTest`(**23 项全绿**)覆盖。 ## 72. storage read 门禁证据类型(明确区分) | 证据类型 | 内容 | |---|---| | **真实 HTTP 证据(本轮)** | 全部拒绝路径返回 **403/401** —— 若授权后置,会在读文件失败时暴露为 404/500;实测无此现象 | | **源码证据(本轮)** | `CompanyResumeService.load` 中 `accessPolicy.authorizeForDownload(...)` **先于** `resumeStorageService.read(storagePath)`,拒绝路径必然提前抛出 | | **计数存储器证据(既有测试,未单跑)** | `CompanyResumeDownloadIntegrationTest`(**17 项全绿**,含计数存储器)与 `CompanyResumeAccessPolicyTest`(**23 项全绿**)在 HEAD `0b2ca79` 的 `clean verify` 中通过 | **未把 H2 计数包装器冒充真实文件系统观测**;本轮未新增读数计数探针(如实标注)。 ## 73. 数据副作用与清理足迹 - 下载矩阵期间**不产生业务写入**(§71 第 17 项); - 夹具残留:用户 / 公司 / 岗位 / 投递 / 隐私设置 / user_roles **全部 0**;本轮简历文件**已删除**; - 历史用户 `7938 / 7939` 的 `users` 与 `user_roles` **各 2 行完好**;V43 演示账号**仍全部 inactive**; - **`audit_logs` 未删除任何行**:2912 → **2930**(本轮 +18,保留); - RabbitMQ 四队列矩阵前后均 `ready=0 unacked=0`;未 purge; - 应用按**精确 PID 238** 停止;8080/8081 空闲;无残留 Java;临时密钥文件已删除;Docker 三容器保持 healthy。 ## 74. 本轮脚本缺陷(非产品缺陷,已修正并记录) | # | 现象 | 判定 | 修正 | |---|---|---|---| | P-1 | `Duplicate entry '81-8063' for key uk_applications_job_student` | **脚本缺陷**:MySQL 默认排序规则**大小写不敏感**,`title='…_job_submitted'` 命中了先插入的 `…_job_SUBMITTED`,取错岗位 | 岗位查询改为 `ORDER BY id DESC LIMIT 1` | | P-2 | `phone must contain exactly 11 digits` | **脚本缺陷**:本地 `ph()` 与 `seed_sms_codes/login` 用的 `lib.phone()` **不是同一个号码**(同类问题第二次出现) | 统一复用 `lib.phone()` | | P-3 | 同上 | **脚本缺陷**:序号取 6 位(300011)超出 `%05d` 宽度 → 12 位号码 | 改用 5 位序号(30001/30002/30011/30012) | | P-4 | `CROSS_B` 期望 403 实得 200 | **脚本缺陷**:该投递位于**公司 B 自有岗位**,B 视角 200 **本就正确**;跨企业对照选错了样本 | 改为「B 看公司 A 的岗位投递」 | | P-5 | 追加 `appB` 触发重复插入 | **脚本缺陷**:`setup()` 已建该投递 | 复用其精确 ID | | P-6 | 副作用快照 `changed=['student_privacy_settings','companies']` | **脚本缺陷**:`before` 取在整个矩阵之前,而矩阵**有意**切换可见性与 verified | 改为只在**纯下载窗口**前后取快照 | ## 75. 产品缺陷 **本轮未发现产品缺陷**。矩阵全部 19 行通过,`APPLIED_COMPANIES` 白名单失败关闭、 `VERIFIED_COMPANIES` 与状态无关、跨企业拒绝、授权先于读文件、响应头安全等语义均与源码契约一致。 ## 76. 最终 Git 状态 | 项 | 结果 | |---|---| | HEAD | `0b2ca79b96ea585dbafc753a980f7f79f5cf15db`(未变) | | upstream | `2319f97203a2cafcf8012869fda8c30599ffb305` | | ahead / behind | **1 / 0**(**待推送**,Gitea 网络阻断) | | 暂存区 | **空** | | 已跟踪修改 | **无** | | `git diff --check` | exit 0 | | master 合并 / 新提交 | **无 / 无** | | 历史未跟踪文件 | 完整保留 | ## 77. 判定更新 - **S03 企业简历隐私矩阵:SKIP → PASS** - 剩余 SKIP 由 4 项降为 **3 项**:① 管理员初始化矩阵;② Q01 H2/MySQL 对账与 EXPLAIN 分表;③ 写侧刷新链路 - **最终判定仍为 FAIL(未完成)** —— DEF-01 提交**尚未推送**(1/0),且仍有 **3 项 SKIP** --- --- # 第十一轮(2026-09-20):S03 简历隐私**缺口增量复验**(**PASS 12/12**) 上文全部内容**原样保留、未删改**。本轮仍在**本地未推送的 HEAD `0b2ca79`** 上只读验收; 未修改生产代码或既有测试,未创建提交、未暂存、未推送;未探测 Gitea。 ## 78. 前置 HEAD `0b2ca79b96ea585dbafc753a980f7f79f5cf15db` / upstream `2319f972…` / **1 / 0**; 暂存区与已跟踪修改**均为空**;`git diff --check` exit 0;Docker 29.6.2,三容器 healthy; 前缀残留 0;历史 `7938/7939` = 2;`audit_logs` 2930。 ## 79. 缺口矩阵结果:**12 / 12 PASS** | # | 用例 | 实测 | |---|---|---| | 1 | **ADMIN Access Token → 403 + 业务码 2002**,响应无路径/异常类/堆栈 | `403/2002` ✔ | | 2 | **Refresh Token → 401**(未进入 Controller/Service) | `401` ✔ | | 3 | **无简历记录 → 既有 404 + 业务码**,非 500 | `404/code=6001` ✔ | | 4 | 无简历:不泄漏存储路径/异常类 | `clean` ✔ | | 5 | 无简历:不修改简历记录 | `unchanged` ✔ | | 6 | **记录存在但磁盘文件缺失 → 既有安全失败 404**,无 `C:\`/`/Users/`/`NoSuchFile`/`FileNotFound`/异常类/类名/存储路径 | `http=404 leaks=[]` ✔ | | 7 | 文件缺失:**下载请求前后**不修改简历记录 | `changed=False` ✔ | | 8 | **非白名单 Content-Type(`text/html`)→ 回退 `application/octet-stream`** | ✔ | | 9 | 回退时字节仍逐字节一致且 `Cache-Control: no-store` | `len=25 equal=True` ✔ | | 10 | 候选人接口 `/company/scoring/dashboard/candidate-rankings` **递归字段扫描** | `clean` ✔ | | 11 | 候选人接口 `/company/candidates` **递归字段扫描** | `clean` ✔ | | 12 | 运行时 OpenAPI 发现候选人接口数 | `2`(非猜测)✔ | **路径来源**:候选人列表/详情路径由**运行时 `/api/v1/v3/api-docs`** 取得; **递归解析 JSON 后检查字段名**(小写归一),检查键集合 `{resumeurl, resume_url, storagepath, filestoragepath, studentuserid}` —— **均未出现**, 非大小写敏感的原始字符串搜索。 ## 80. 文件读取门禁(证据分层,未混淆) | 路径 | 本轮证据 | |---|---| | ADMIN | 真实 HTTP **403/2002** —— 在 `userRoleGuard.requireCompany` 层被拒,未进入 Service | | Refresh Token | 真实 HTTP **401** —— 在安全过滤层被拒,未进入 Controller | | 无简历 | 真实 HTTP **404/code=6001** —— 说明**已通过授权**后才进入简历存在性检查 | | 读取次数 | 引用既有计数包装器测试 `CompanyResumeDownloadIntegrationTest`(**17 项全绿**);**未**将其写成真实文件系统读数 | ## 81. 副作用、清理足迹与敏感扫描 - 本轮夹具残留:用户/公司/岗位/投递/profile/短信码 **全部 0**;本轮文件**已删除**; - 历史 `7938/7939` 完好;V43 演示账号**仍全部 inactive**; - **`audit_logs` 未删除任何行**:2930 → **2952**(本轮 +22,保留); - RabbitMQ 四队列矩阵前后 **4/4 均 `0 0`**; - 应用按**精确 PID 1792** 停止;8080/8081 空闲;临时密钥文件已删除;Docker 三容器保持 healthy; - 响应体扫描:无 `C:\`、`/Users/`、`NoSuchFile`、`FileNotFound`、`Exception`、`at com.`、`java.`、存储路径 —— 全部 0 命中。 ## 82. 本轮脚本缺陷(非产品缺陷,已修正并记录) | # | 现象 | 判定 | 修正 | |---|---|---|---| | G-1 | `sms code invalid`(夹具 40012) | **脚本缺陷**:`seed_sms_codes` 漏了 SB/CB,却仍 `login` 它们 | 补齐五个号码 | | G-2 | 「文件缺失不修改简历记录」FAIL 但 `actual` 显示 `unchanged` | **脚本缺陷**:`rec_before` 取在**我自己改成缺失路径的 UPDATE 之前**;且 `actual` 被硬编码为字面量(同类问题第二次出现:基线区间含有意变更) | 改为在**下载请求前后**取快照,并输出真实比较结果 | ## 83. 产品缺陷 **本轮未发现产品缺陷**。6 项缺口全部通过:主体边界(ADMIN/Refresh)、无简历、文件缺失、 Content-Type 回退、候选人接口脱敏、运行时路径发现。 ## 84. 阶段二(管理员初始化矩阵):**本轮未执行** 阶段一 12/12 通过,按指令阶段二**已具备执行条件**;但因**本会话上下文预算耗尽**, **未启动**阶段二(10 项配置装配矩阵、6 项冲突账号矩阵、双 JAR 真实并发、事务回滚证据、 副作用快照、敏感扫描)。**如实标注为未执行**,不计入 PASS,也不写成跳过。 **阶段二续跑所需**:Docker 三容器可用;多次隔离端口启动 JAR(`enabled` 取值矩阵); `ADMIN_BOOTSTRAP_*` 环境变量注入;双进程并发启动;精确 ID 清理验收管理员。 ## 85. 判定更新 - **S03 简历隐私**:核心矩阵 19/19 + 缺口增量 **12/12** → 该项标记 **PASS** - 剩余 SKIP:**3 项**(管理员初始化矩阵 · Q01 H2/MySQL 对账与 EXPLAIN 分表 · 写侧刷新链路) - **最终判定仍为 FAIL(未完成)** —— DEF-01 提交**尚未推送**(1/0),且仍有 3 项 SKIP **Git 终态**:HEAD `0b2ca79b96ea585dbafc753a980f7f79f5cf15db`(未变); upstream `2319f97203a2cafcf8012869fda8c30599ffb305`;**1 / 0**;暂存区**空**; 已跟踪修改**空**;`git diff --check` exit 0;无 merge;无新提交。 --- --- # 第十二轮(2026-09-20):S03 管理员初始化矩阵 —— **契约确认完成,矩阵执行 SKIP** 上文全部内容**原样保留、未删改**。本轮只读验收,未改生产代码/测试/迁移/配置,未 add/commit/push, 未重建容器,未探测 Gitea。 ## 86. 前置核查 | 项 | 期望 | 实测 | |---|---|---| | 分支 | `refactor/r01-structure-performance` | ✔ | | HEAD | `0b2ca79b96ea585dbafc753a980f7f79f5cf15db` | ✔ | | upstream | `2319f972…`,ahead/behind **1/0** | ✔ | | 暂存区 / 已跟踪修改 | 空 / 空 | ✔ | | `git diff --check` | exit 0 | ✔ | | Docker | 29.6.2,`mydreamjob-mysql` / `-redis` / `-rabbitmq` 均 Up (healthy) | ✔ | | 8080 / 8081 | 空闲 | ✔ | ## 87. 真实契约(**以仓库源码为准,非按旧报告猜测**) **1. 环境变量名称**(`application.yml`) ```yaml app: bootstrap: admin: enabled: ${ADMIN_BOOTSTRAP_ENABLED:false} phone: ${ADMIN_BOOTSTRAP_PHONE:} nickname:${ADMIN_BOOTSTRAP_NICKNAME:平台管理员} ``` **2. `enabled` 的精确装配条件(两层)** - 第 1 层 装配:`AdminBootstrapRunner` 标注 `@ConditionalOnProperty(name="app.bootstrap.admin.enabled", havingValue="true")`; - 第 2 层 运行期兜底字面量防护(`run()` 首行): ```java if (!STRICTLY_ENABLED.equals(properties.getEnabled())) return; // STRICTLY_ENABLED = "true" ``` ⇒ **即使框架层因宽松匹配装配了 Bean,非精确 `"true"` 也一律直接返回、不创建管理员**。 `TRUE/True/yes/1/on` 的最终行为是**不创建**;拒绝发生在 **Runner 字面量防护层**。 **3. 手机号格式要求**:`Pattern.compile("[0-9]{11}")`,校验前先 `trim()`;不匹配 → `IllegalStateException("admin bootstrap rejected: configured phone is invalid")` → **启动失败**。 `null` 或 `""` 同样不匹配 → 启动失败。 **4. 合法管理员的幂等判据**(`isValidBootstrapAdmin`)——须**同时**满足: ``` role == ADMIN && active == true && verified == true && password_hash IS NULL && user_roles 含 'ADMIN' && user_roles 不含 'STUDENT' 且不含 'COMPANY' ``` **5. 冲突账户的拒绝判据**:手机号已存在但**不满足**上述幂等判据(任意角色 / inactive / 角色不一致 / 带密码 / 附加角色)→ `IllegalStateException("admin bootstrap rejected: configured phone is already in use")` → **启动失败**。另有前置短路:**已存在活动 ADMIN** 时**无副作用跳过**(不创建第二个)。 **6. `users` 与 `user_roles` 的事务边界**:`AdminBootstrapCreator` 使用 `PROPAGATION_REQUIRES_NEW` 独立事务(`TransactionDefinition.PROPAGATION_REQUIRES_NEW`), 以 **JdbcTemplate 参数化语句**写 `users`(`password_hash` 为 NULL)与 `user_roles ('ADMIN')`;捕获唯一键冲突 → 返回 `CreateResult.CONFLICT`(只回滚本次创建)。 **7. 是否创建密码**:**否** —— INSERT 中 `password_hash` 为 NULL,幂等判据要求 `passwordAbsent`。 **8. 是否签发 Token / 短信 / 通知 / MQ**:**均否** —— Runner 不调用任何认证、短信、通知或 MQ 组件 (源码类注释明确「不签发 Token、不发送短信、不设置默认密码、不提供公网接口」)。 **9. 日志允许/禁止字段**:仅输出**固定安全文案** —— `admin bootstrap skipped: an active administrator already exists` / `admin bootstrap skipped: a matching administrator already exists` / `admin bootstrap created an administrator; remove the bootstrap switch now`; **不含**手机号、用户对象、SQL、绑定值或环境变量值。 ## 88. 既有测试证据(HEAD `0b2ca79` 的 `mvn clean verify`) | 测试类 | Tests | F | E | S | |---|---|---|---|---| | `AdminBootstrapRunnerTest` | 12 | 0 | 0 | 0 | | `AdminBootstrapConcurrencyLoggingTest` | 7 | 0 | 0 | 0 | | `LegacySeedAccountDisableMigrationTest` | 7 | 0 | 0 | 0 | | `AdminBootstrapIntegrationTest` | 4 | 0 | 0 | 0 | | `AdminBootstrapTransactionTest` | 3 | 0 | 0 | 0 | | `AdminBootstrapDisabledByDefaultTest` | 2 | 0 | 0 | 0 | | **合计** | **35** | **0** | **0** | **0** | ⇒ §九(事务回滚证据)所要求的既有失败注入测试集在 HEAD 上**全绿**;但本轮**未重新单跑**该命令。 ## 89. 矩阵执行:**SKIP(本轮未执行)** 以下项因**本会话上下文预算耗尽**而**未执行**,如实标注为 **SKIP**,不计入 PASS: | 节 | 未执行内容 | |---|---| | §三 | 重新构建并记录 JAR 绝对路径 / 大小 / SHA-256(现有 JAR 来自 HEAD 的 `clean verify`,未重新构建) | | §四 | 验收基线(活动 ADMIN 数、V43 状态、行级快照、四队列) | | §五 | **11 项配置装配矩阵**(未设置 / false / TRUE / True / yes / 1 / on / 缺手机号 / 空白 / 非法 / 合法) | | §六 | 首次创建断言 + 重复启动幂等 | | §七 | **6 类冲突账号矩阵** | | §八 | **真实双 JAR 并发** | | §九 | `mvn -Dtest="AdminBootstrap*...,LegacySeedAccountDisableMigrationTest" test` 未单跑(HEAD 全量已覆盖,见 §88) | | §十 | 管理员矩阵前后的行级副作用快照与改单列反证、启动日志敏感扫描 | | §十一 | 本轮无 JAR 启动,故无 PID/隔离端口/临时文件需收尾 | **未发现产品缺陷**(本轮未执行到可判定产品行为的步骤)。 ## 90. 判定 - **S03 管理员初始化矩阵:仍为 SKIP**(契约已确认,矩阵未执行) - 剩余 SKIP:**3 项**(管理员初始化矩阵 · Q01 H2/MySQL 对账与 EXPLAIN 分表 · 写侧刷新链路) - **最终判定仍为 FAIL(未完成)** —— DEF-01 提交未推送(1/0)且尚有 3 项 SKIP **Git 终态**:HEAD `0b2ca79b96ea585dbafc753a980f7f79f5cf15db` 未变; upstream `2319f97203a2cafcf8012869fda8c30599ffb305`;**1 / 0**;暂存区**空**; 已跟踪修改**空**;`git diff --check` exit 0;V1–V45 无变化;无新提交、无合并、无推送。 ### 90.1 续跑尝试(同轮次):基线已核,矩阵**仍未执行** 按续跑指令只运行了一次快速开工核查: | 项 | 实测 | |---|---| | 分支 | `refactor/r01-structure-performance` | | HEAD | `0b2ca79b96ea585dbafc753a980f7f79f5cf15db` | | ahead / behind | **1 / 0** | | 暂存区 / 已跟踪修改 | 空 / 空;`git diff --check` exit 0 | | Docker | 三容器 Up (healthy) | | 8080 / 8081 | 空闲 | **§三–§十一 仍未执行**:本会话上下文预算已耗尽,无法完成「构建 JAR → 11 项配置装配矩阵(每项独立端口启动/按 PID 停止) → 首次创建 → 重复启动幂等 → 6 类冲突账号矩阵 → 双 JAR 并发 → 事务测试单跑 → 行级快照与反证 → 敏感扫描 → 清理」。 **未启动任何 JAR**,故数据库**未被本轮改动**,无需清理;`audit_logs` 无本轮增量。 **未发现产品缺陷**(未执行到可判定产品行为的步骤)。**本轮不写完成语**。 **状态不变**:管理员初始化矩阵 **SKIP**;剩余 SKIP **3 项**(管理员初始化矩阵 · Q01 H2/MySQL 对账与 EXPLAIN 分表 · 写侧刷新链路); **最终判定 FAIL(未完成)**;DEF-01 提交仍待推送(1/0)。 --- ## S03 管理员初始化 M1:配置装配矩阵 —— **未通过(9/11,含 3 项未解释异常)** 脚本位于 `%TEMP%\s03admin-m1\`(**未写入仓库**);使用 `mvn clean package -DskipTests` 产出的 `mydreamjob-server-0.0.1-SNAPSHOT.jar`(**82 433 528 B**,SHA-256 前缀 `3f67fb0710c01fe73aa588a8…`)。 ### 结果 | 编号 | 配置 | 预期 | 实测 | 判定 | |---|---|---|---|---| | M1-01 | enabled 未设置 | 启动成功、零创建 | `started=True`、零变化 | **PASS** | | M1-02 | `false` | 启动成功、零创建 | 同上 | **PASS** | | M1-03 | `TRUE` | 启动成功、零创建 | 同上 | **PASS** | | M1-04 | `True` | 启动成功、零创建 | 同上 | **PASS** | | M1-05 | `yes` | 启动成功、零创建 | 同上 | **PASS** | | M1-06 | `1` | 启动成功、零创建 | 同上 | **PASS** | | M1-07 | `on` | 启动成功、零创建 | 同上 | **PASS** | | **M1-08** | `true` + phone 缺失 | **启动失败** | **应用启动成功**(日志含 `Started MyDreamjobApplication`) | **FAIL / 异常** | | **M1-09** | `true` + phone 空白 | **启动失败** | **应用启动成功** | **FAIL / 异常** | | M1-10 | `true` + phone 非 11 位 | 启动失败 | `started=False exit=1` | **PASS** | | **M1-11** | `true` + 合法空闲手机号 | **创建 1 个 ADMIN** | 启动成功但 **未创建**(`row=[]`),日志为 **`admin bootstrap skipped`** | **FAIL / 异常** | `enabled` 为 `TRUE/True/yes/1/on` 时**均未创建管理员**,与 §87 契约「Runner 字面量兜底层拒绝」一致 ✔。 ### 三项未解释异常(如实记录,**不作猜测性结论**) 1. **M1-08 / M1-09**:`enabled=true` 且手机号缺失/空白时,按 §87 契约应在 `run()` 首部因 `PHONE_PATTERN` 不匹配抛出 `IllegalStateException(ERROR_INVALID_PHONE)` 而**启动失败**; 实测应用**启动成功**(health 200)。 2. **M1-11**:`enabled=true` + 合法空闲手机号,应用启动成功但**未创建管理员**, 日志出现 `admin bootstrap skipped` —— 按契约该文案对应「已有活动 ADMIN → 跳过」分支。 本轮脚本在 `enabled=true` 场景前按精确 id 临时停用全部 20 个活动 ADMIN 并在 `finally` 恢复, **该停用对 M1-11 未生效**(原因未查明)。 3. 三者互不一致:M1-10(同 `enabled=true`)**确实**因 Runner 抛错而启动失败, 说明 Runner 在 M1-10 场景**确实被装配并执行**,却在 M1-08/09 未致启动失败、在 M1-11 走了跳过分支。 **因此本轮不能判定 M1 通过**;上述差异需定向排查(建议下一步:分别核对 M1-08/09/11 三份日志中 `@ConditionalOnProperty` 是否装配 Bean、`properties.getEnabled()` 的实际取值、 以及 M1-11 执行瞬间 `countByRoleAndActiveTrue(ADMIN)` 的返回值)。 ### 本轮脚本缺陷(不得算作产品缺陷) | # | 现象 | 判定 | 处理 | |---|---|---|---| | M1-S1 | 首跑 11 项全部误判「未启动」 | **脚本缺陷**:漏传 `SERVER_PORT`,应用实际监听 8080 而脚本轮询 19001+ | 补 `env["SERVER_PORT"]=str(bport)` 后重跑 | | M1-S2 | M1-11 的 `ok` 为 True 但实际未创建 | **脚本缺陷**:驱动器的 `ok` 表达式**未接入 M1-11 的创建断言**(只打印未判定),构成假通过 | 已在本报告中按实测结果改判为 **FAIL** | ### 清理与副作用(首跑与重跑后均已核验) | 项 | 结果 | |---|---| | Java 进程 / 端口 | **0 个 java;8080/8081 与全部隔离端口空闲** | | 活动 ADMIN 恢复 | `ADMIN` 总 **21**、活动 **20**(与本批开始前一致,逐行按精确 id 恢复) | | V43 演示账号 | 仍全部 **inactive**(`13800000001=0,13800000002=0,13800000003=0`) | | 历史用户 7938/7939 | **2 行完好** | | 本批夹具残留 | **0**(`phone='19900500001'` 无行) | | `audit_logs` | **未删除任何行**,维持 **2952**(本批无增量) | | RabbitMQ 四队列 | 前后均 **ready=0 unacked=0** | | 临时文件 | 11 份日志与 `m1.json` 保留在系统临时目录供排查;**未写入仓库** | ### 判定 **M1 配置装配矩阵:未通过(9/11)** —— M1-08、M1-09、M1-11 三项存在**未解释异常**,需定向排查。 **不写 M1 通过语**;管理员初始化整体仍为 **SKIP/PARTIAL**,最终总验收仍为 **FAIL(未完成)**。 --- ## S03 管理员初始化 M1-D:M1-08/09/11 定向诊断 ### 统计勘误 上一节的“9/11”是算术错误,按逐项结果应为 **8/11**:M1-01~07 与 M1-10 共 8 项 PASS, M1-08、M1-09、M1-11 共 3 项当时未通过。本勘误只修正汇总,不改写首次运行的原始结果。 ### 两项验收驱动器根因 1. **M1-08/09 的假启动成功**:旧驱动器在第一次看到业务健康接口 200 后立即终止 Java。 Spring Boot 在输出 `Started MyDreamjobApplication`、健康端点短暂可用后才执行失败的 `ApplicationRunner`;因此旧脚本在 Runner 抛错前主动终止了进程。旧结果中的 `exit=1` 也是脚本主动终止后的退出码,不能证明稳定启动。 2. **M1-11 的错误 skip**:旧脚本只停用 `user_roles.role='ADMIN' AND users.is_active=1` 的 20 个用户;生产 Repository 实际谓词是 `users.role='ADMIN' AND users.is_active=1`,实测有 **21** 行。差额是一个既有角色表不一致账号 (仅记录 id `7918`,未修改它的角色数据)。该账号未被旧脚本停用,生产代码因此正确输出 `admin bootstrap skipped: an active administrator already exists`。 ### 确定性重跑方法 - 清除父进程中的三个 `ADMIN_BOOTSTRAP_*` 环境变量; - 仅使用 Spring Boot 命令行属性传入 `app.bootstrap.admin.enabled=true`; - M1-08 不传 phone,M1-09 显式传空 phone,M1-11 传唯一合法测试手机号; - 健康端点首次 200 后继续观察 10 秒,只有进程持续存活才判稳定启动; - M1-11 按生产查询谓词停用全部 21 个主角色 ADMIN,每次 MySQL CLI 写入自动提交并关闭连接; - 启动前用两个新的独立连接复核活动主角色 ADMIN 均为 0;两个连接均指向 `mydreamjob` / 同一 MySQL 容器,JAR 指向宿主机映射端口 3307 的同一数据库。 ### 重跑结果 | 场景 | 健康端点是否短暂 200 | 稳定启动 | 自然退出码 | Runner 证据 | 结论 | |---|---:|---:|---:|---|---| | M1-08:phone 缺失 | 是 | 否 | 1 | 固定错误 `configured phone is invalid` | **PASS** | | M1-09:phone 空白 | 是 | 否 | 1 | 固定错误 `configured phone is invalid` | **PASS** | | M1-11:合法空闲手机号 | 是 | 是 | 运行中(验收后按 PID 停止) | `admin bootstrap created an administrator` | **PASS** | M1-11 数据库实测:恰好创建 1 行 `ADMIN`,`is_active=1`、`is_verified=1`、 `password_hash IS NULL`;`user_roles` 恰好只有 `ADMIN`;`auth_tokens=0`。 ### 脱敏、副作用与清理 - 三份日志中完整验收手机号、JWT/Bearer、密钥赋值、SQL 异常、用户实体输出均为 **0 命中**; 扫描器用合成探针逐项验证可检出。 - 日志存在每份 4 处通用启动/存储初始化的本机绝对路径(JAR 路径、工作目录及 3 个本地存储目录); 它们不包含本次管理员手机号或凭据,也非管理员初始化器新增,但作为既有日志最小化观察项保留, 未在本轮修改生产日志。 - RabbitMQ 四队列最终均 `ready=0 / unacked=0`;`audit_logs` 仍为 2952,本轮未删除审计记录。 - 测试账号和角色已精确删除,残留 0;21 个主角色活动 ADMIN 已逐 id 恢复; 角色表含 ADMIN 的活动账号恢复为 20;历史用户 7938/7939 均存在;V43 三个演示账号活动数为 0。 - 三个 Java 进程均已退出,诊断业务/管理端口均空闲;脱敏诊断证据保留在系统临时目录。 ### M1 修订判定 原先三项异常均由验收驱动器造成,未发现管理员初始化生产缺陷。结合首次执行已经通过的 8 项, **M1 配置装配矩阵修订为 11/11 PASS**。 > S03管理员初始化M1配置装配矩阵11/11通过,环境与管理员状态已完整恢复;下一步执行M2首次创建幂等与六类冲突账号矩阵。 --- ## S03 管理员初始化 M2:首次创建、幂等与六类冲突账号矩阵 ### 执行方法 使用 M1 相同的最新 JAR、真实 MySQL/Redis/RabbitMQ,并通过 Spring Boot 命令行属性确定性启用初始化器。 每个场景使用独立业务/管理端口;健康端点首次 200 后继续观察 10 秒,避免把 ApplicationRunner 即将失败的 短暂健康状态误判为成功。场景开始前按生产谓词暂时停用 21 个既有活动主角色 ADMIN,场景结束后逐 id 恢复。 ### 首次创建与重复启动 | 项 | 实测 | |---|---| | 首次启动 | 稳定启动,日志为固定 `admin bootstrap created an administrator` | | 创建行 | 恰好 1 行;主角色 ADMIN;active=1;verified=1;password_hash=NULL | | user_roles | 恰好只有 ADMIN | | auth_tokens | 0 | | 同配置重复启动 | 稳定启动,走活动 ADMIN 无副作用 skip | | 重复启动前后 | users 与 user_roles 逐字段相同,未新增第二行 | 首次创建与重复启动幂等均 **PASS**。 ### 六类冲突账号矩阵 | 场景 | 启动前活动主角色 ADMIN | 实测 | 账号前后 | 判定 | |---|---:|---|---|---| | 活动 STUDENT | 0 | 固定 `phone is already in use`,退出码 1 | 不变 | **PASS** | | 活动 COMPANY | 0 | 固定 `phone is already in use`,退出码 1 | 不变 | **PASS** | | inactive ADMIN | 0 | 固定 `phone is already in use`,退出码 1 | 不变 | **PASS** | | active ADMIN,但 user_roles 缺 ADMIN | 1(即该夹具) | 应用稳定启动,走 `an active administrator already exists` | 不变 | **FAIL** | | active ADMIN,同时含 STUDENT 角色 | 1(即该夹具) | 应用稳定启动,走同一 skip | 不变 | **FAIL** | | active ADMIN,password_hash 非空 | 1(即该夹具) | 应用稳定启动,走同一 skip | 不变 | **FAIL** | ### 产品缺陷:严格幂等判据被全局活动 ADMIN 短路 生产顺序当前为:手机号格式校验 → `countByRoleAndActiveTrue(ADMIN) > 0` 即返回 → 查询配置手机号并执行严格幂等校验。 后三种夹具本身的 `users.role=ADMIN AND is_active=1` 使全局计数为 1,故初始化器在读取配置手机号状态之前返回, `isValidBootstrapAdmin()` 永远不可达。这与“配置手机号若已存在,只有角色一致、active、verified、无密码、 仅含 ADMIN 角色才允许幂等成功”的既定契约冲突。 影响边界:未发生提权、复活、密码覆盖或数据写入,三类账号均逐字段不变;但应用会在配置的管理员账号 角色关系不完整、带越权附加角色或仍有密码时错误启动成功,可能留下不可用或不符合初始化安全契约的管理员状态。 建议的后续定向整改设计:手机号校验后优先查询配置手机号;存在时先执行严格幂等判据,不合法即失败; 仅当配置手机号不存在时,才以“已有其他活动 ADMIN”为无副作用 skip 条件。该方案需先补修复前失败测试, 本轮验收未修改生产代码。 ### 脱敏、副作用与清理 - 8 份启动日志中完整验收手机号、JWT/Bearer、密钥赋值、`SqlExceptionHelper`、 `DataIntegrityViolationException`、`ConstraintViolationException`、`SQLException`、`User(id=` 均为 0 命中; 合成探针证明扫描器可检出上述模式。 - 首次创建之外,各冲突账号请求前后逐字段不变;重复启动没有新增用户、角色或 Token。 - 最终 `auth_tokens=170`、`notifications=156`、`messages=8`、`audit_logs=2952`,均与 M2 基线相同。 - RabbitMQ 四队列最终均 `ready=0 / unacked=0`。 - 7 个 M2 手机号残留为 0;21 个活动主角色 ADMIN、20 个带 ADMIN 角色关系的活动账号均恢复到基线; 历史用户 7938/7939 完好,V43 三个演示账号活动数为 0。 - 全部 M2 Java 进程已按 PID 结束,隔离端口已释放;日志与脱敏 JSON 证据仅保留在系统临时目录。 ### M2 判定 **M2 FAIL:首次创建与重复启动幂等通过,六类冲突中 3 类通过、3 类失败。** 已复现生产逻辑缺陷,不进入 M3,不写管理员初始化整体验收通过语。 ### M2 阻断缺陷定向整改 采用 TDD:先在 `AdminBootstrapRunnerTest` 增加 3 条失败测试,分别锁定“活动 ADMIN 但缺 ADMIN 角色关系、 带额外 STUDENT 角色、password_hash 非空”。修复前实测 **15 项中 3 项失败**,均为“期望 IllegalStateException,但实际未抛出”。 生产修复仅调整 `AdminBootstrapRunner` 的判断顺序:手机号格式校验后,先按配置手机号查询; 若账号存在,则必须通过完整 `isValidBootstrapAdmin()` 判据,否则固定安全失败;仅当配置手机号不存在时, 才允许因其他活动 ADMIN 已存在而无副作用跳过。未修改创建事务、并发冲突处理、日志文案、配置、迁移或接口。 既有手机号冲突测试中不再需要全局 ADMIN 计数 stub;删除 5 处无效 stub,未修改任何业务断言。 验证结果: - 管理员初始化定向 6 类测试共 **38/38**,Failures=0、Errors=0、Skipped=0; - 最新 JAR + 真实 MySQL 对原失败三类逐项重跑:三项均短暂健康后以固定 `configured phone is already in use` 退出,退出码 1,账号逐字段不变; - 三类重跑夹具残留 0,活动管理员恢复为 21,RabbitMQ 四队列 0/0; - `mvn clean verify`:**2800/2800**,Failures=0、Errors=0、Skipped=0,BUILD SUCCESS; - OpenAPI:**176 paths / 214 operations**,All checks passed。 **M2 阻断缺陷整改后复验通过:首次创建、重复启动幂等及六类冲突账号矩阵全部满足契约。** 下一步方可执行 M3 双 JAR 并发、事务回滚和统一敏感扫描。 ## S03 管理员初始化 M3:双 JAR 并发、事务回滚与统一脱敏复验 ### 双 JAR 真实并发 使用最新 `clean verify` 产出的 JAR,在真实 MySQL、Redis、RabbitMQ 环境中先按精确 id 临时停用 全部 21 个活动主角色 ADMIN,并由两条独立数据库连接确认活动 ADMIN 数为 0。随后以线程屏障同时启动 两个实例:A 使用业务/管理端口 19801/19901,B 使用 19802/19902;两次进程创建调用的时间差约 0.215 ms。 两个实例均稳定启动。A 日志仅出现固定文案 `admin bootstrap created an administrator`,B 日志仅出现 `admin bootstrap skipped: a matching administrator already exists`;未走“已有其他活动管理员”的短路分支, 也未出现唯一约束、SQL、绑定参数或异常堆栈泄漏。 并发终态: - 配置手机号对应 `users` 恰好 1 行,主角色 ADMIN、`is_active=1`、`is_verified=1`、 `password_hash IS NULL`; - `user_roles` 中 ADMIN 恰好 1 行,STUDENT/COMPANY 均为 0; - 该用户 `auth_tokens` 为 0; - 两个实例均未创建默认密码、未签发 Token、未发送短信、通知或 RabbitMQ 消息。 ### 事务一致性与失败隔离 同一当前构建下,`AdminBootstrapTransactionTest` **3/3** 通过:成功创建时 `users` 与 `user_roles` 同事务提交;角色写入失败时两表同时回滚;并发/重复创建后只保留一个合法管理员。 管理员初始化六类定向测试合计仍为 **38/38**,Failures=0、Errors=0、Skipped=0。 ### 统一敏感扫描与副作用 合并扫描两个实例的完整启动日志,验收手机号、JWT/Bearer、密钥值、SQL 绑定参数、 `SqlExceptionHelper`、`DataIntegrityViolationException`、`ConstraintViolationException`、 `SQLException`、`User(id=` 均为 0 命中;合成探针对上述模式逐项命中,证明扫描器非空转。 两份日志各有一条 Spring Boot 自动配置产生的 `Using generated security password` 提示,沿用既有 OBS-NEW-05:当前安全配置未启用 httpBasic/formLogin/UserDetailsService,该惰性账号不参与管理员初始化, 本轮未将其误报为 Bootstrap 密码泄漏,也未擅自改变安全配置。 并发窗口前后 `auth_tokens=170`、`notifications=156`、`messages=8`、`audit_logs=2952`, 均逐项不变;RabbitMQ 四队列均为 `ready=0 / unacked=0`。并发夹具已精确删除,21 个活动主角色 ADMIN 已逐行恢复,历史用户 7938/7939 完好,V43 三个演示账号仍为 inactive。 两个 Java 进程均按精确 PID 停止,8080/8081 与四个隔离端口全部释放;日志与结果 JSON 仅保留在 系统临时目录,未进入仓库。 ### M3 判定 **M3 PASS:双 JAR 并发只产生一个合法管理员,事务回滚、日志脱敏、业务副作用与环境清理均满足契约。** 管理员初始化的 M1 配置装配、M2 幂等与冲突矩阵、M3 并发与事务三阶段现已全部通过。下一步仅进行 M4 管理员初始化证据汇总与最终边界复核;不得因此提前宣称全项目最终验收通过,Q01 跨库 EXPLAIN、 写侧刷新链路以及 DEF-01 待推送状态仍需独立收口。 ## S03 管理员初始化 M4:证据汇总与最终边界复核 ### 判据汇总 | 阶段 | 覆盖范围 | 最终结果 | | --- | --- | --- | | M1 | 默认关闭、`false`、大小写/别名非字面量、缺失/空白/非法/合法手机号共 11 项配置装配矩阵 | **11/11 PASS** | | M2 | 首次创建、重复启动幂等、STUDENT/COMPANY/inactive/角色关系缺失/附加角色/password 非空六类冲突 | **全部 PASS** | | M3 | 双 JAR 真实并发、事务回滚、统一日志脱敏、业务副作用与清理 | **PASS** | | 自动化 | 管理员初始化定向测试 | **38/38,F=0 E=0 S=0** | | 全量 | `mvn clean verify` | **2800/2800,F=0 E=0 S=0** | | 契约 | OpenAPI | **176 paths / 214 operations** | M1 初次脚本误判和 M2 初次发现的三个生产阻断均保留在前文,未覆盖历史记录。M2 的最终生产修复范围仅为 `AdminBootstrapRunner`:配置手机号存在时先执行严格幂等校验;只有配置手机号不存在时,其他活动 ADMIN 才可触发无副作用跳过。对应测试新增 3 条阻断回归用例,未修改公开配置、事务创建器、数据库结构或接口。 ### 最终边界复核 - 当前真实生产代码差异恰好 1 个文件:`AdminBootstrapRunner.java`(9 行新增、7 行删除); 测试差异恰好 1 个文件:`AdminBootstrapRunnerTest.java`(29 行新增、4 行删除)。 - 暂存区为空,未执行提交或推送;本轮生产/测试差异的敏感模式扫描为 0 命中。 - `CandidateOverviewStatusPolicy.java` 与 `JobPreferenceScoreCalculator.java` 虽被 Git 标记为工作区修改, 但工作区与 HEAD 的 blob hash 分别完全相同,属于 Windows stat 缓存假阳性,不计入内容差异。 - 无 Java 进程;8080/8081、19801/19901、19802/19902 均无监听。 - 管理员并发夹具残留 0;活动主角色 ADMIN 为 21,活动 ADMIN 角色关系为 20,历史用户 7938/7939 均存在,V43 三个演示账号活动数为 0;`audit_logs=2952` 且未删除历史审计。 - M3 前后 `auth_tokens=170`、`notifications=156`、`messages=8`;RabbitMQ 四队列均保持 `ready=0 / unacked=0`。 - 未修改 V1–V45、表、索引、`ErrorCode`、DTO、依赖或 OpenAPI;未进入 Q01 或写侧刷新实现。 ### S03 管理员初始化最终结论 **S03 管理员初始化真实 MySQL 配置、幂等、冲突、并发、事务、脱敏与副作用矩阵全部通过,可以冻结该验收项。** 该结论只关闭管理员初始化验收项,不等于全项目最终验收通过。当前仍需完成 Q01 H2/MySQL 对账与 EXPLAIN 分表证据、评分写侧刷新链路验收,并处理 DEF-01 本地提交领先远端 1 个提交的推送状态。 ## Q01 H2/MySQL 逐项对账与 EXPLAIN 分表最终复验 ### 执行环境与测试结果 Docker Server 29.6.2 可用,Testcontainers 实际启动 `mysql:8.4`,执行正式 Flyway 迁移并通过 Hibernate schema validation;本轮不存在 `disabledWithoutDocker` 跳过。 | 层级 | 测试数 | Failures | Errors | Skipped | | --- | ---: | ---: | ---: | ---: | | H2 Specification | 21 | 0 | 0 | 0 | | H2 Service | 9 | 0 | 0 | 0 | | H2 行级副作用 | 4 | 0 | 0 | 0 | | MySQL 8.4 Specification | 15 | 0 | 0 | 0 | | MySQL 8.4 行级副作用 | 4 | 0 | 0 | 0 | | MySQL 8.4 EXPLAIN | 1 | 0 | 0 | 0 | | **Q01 合计** | **54** | **0** | **0** | **0** | 另行重跑 DEF-01 入口回归 **9/9**,覆盖 null/缺失/空/空白/trim 关键词、DRAFT 排除、分页、排序和 只读副作用,Failures=0、Errors=0、Skipped=0。 ### H2/MySQL 语义对账 两库共同覆盖并取得一致结果的契约包括:关键词 trim、null/空白不施加过滤、标题/描述/要求字段匹配、 关键词大小写、城市与用工类型的 trim 精确匹配及大小写变体、薪资区间重叠与边界相等、NULL 薪资的 SQL 三值逻辑、公司精确过滤、多条件 AND、非公开岗位排除、空结果、稳定 latest 分页和 salary 排序。 H2 侧保留更细的单维度拆分用例,MySQL 侧将若干相同契约合并在一个参数化/综合用例内;因此测试方法 数量不要求相等,但相同业务条件的 ID 集合、顺序和边界期望一致。查询副作用在两库均为 4/4:真实查询 前后行级稳定字段完全相同,且插入、删除、改单字段三类反证都能被快照检测,证明零变化比较非空转。 ### MySQL EXPLAIN 分表证据 本次重新生成 `target/q01-explain-report.md`,EXPLAIN 使用与 Criteria 查询相同 SQL 和参数,并先断言 两者返回的岗位 ID 与顺序一致。真实结果如下: | SQL | `jobs` access_type | possible_keys | filesort | temporary | | --- | --- | --- | --- | --- | | 组合过滤 + latest 分页 | `ALL` | company/status、status/created、status/employment、status/salary | true | false | | latest 分页 | `ALL` | status/created、status/employment、status/salary | true | false | | salary 排序 | `ALL` | status/created、status/employment、status/salary | true | false | | count | `ALL` | company/status、status/created、status/employment、status/salary | false | false | 夹具按城市仅构造 1/20/50 条岗位,优化器在该小样本上选择全表扫描;该结果只证明 SQL 形状与当前执行 计划,不能外推为生产规模性能结论,也不能误写成“命中索引”。关键词 `%keyword%` 前导通配符和排序 filesort 仍是已知性能风险。本轮不新增 V46、不修改索引或查询语义。 SQL 数量证据:匹配 1/20/50 条时 Repository 分别执行 1/2/2 条 SQL,完整 Service 分别为 2/3/3 条;结果规模增长不会形成逐岗位查询,未发现 N+1。 ### Q01 最终判定与边界 **Q01 H2/MySQL 查询语义、行级副作用、真实 MySQL EXPLAIN 与 DEF-01 回归全部通过,可以冻结 Q01 最终验收项。** 本轮未修改任何生产代码、测试、迁移、索引、依赖或 OpenAPI;Testcontainers 容器已由 Ryuk 回收, 项目既有 MySQL/Redis/RabbitMQ 容器保持运行。全项目最终验收目前只剩评分写侧刷新链路,以及本地未提交 的管理员初始化修复和 DEF-01 领先远端 1 个提交的 Git 收口。 ## R01-D4 评分写侧刷新链路最终复验 ### 本轮新鲜自动化证据 使用当前工作区代码重新执行刷新基础设施、写侧登记与事务原子性测试,Docker/Testcontainers 实际启动 MySQL 8.4,结果 **67/67**,Failures=0、Errors=0、Skipped=0: | 测试类 | 数量 | 核心覆盖 | | --- | ---: | --- | | `CandidateScoringRefreshBatchIsolationTest` | 4 | 单任务失败不阻断同批正常任务 | | `CandidateScoringRefreshClaimFenceTest` | 6 | 旧领取者不得覆盖新领取者 | | `CandidateScoringRefreshClaimTokenTest` | 13 | 不可复用 claim token、迟到完成/失败/回退均安全 | | `CandidateScoringRefreshFailureTransactionTest` | 3 | 源业务回滚时登记同步回滚 | | `CandidateScoringRefreshInfrastructureTest` | 14 | 登记合并、重开、PROCESSING 防丢失、八维写入与版本幂等 | | `CandidateScoringRefreshMySqlIntegrationTest` | 6 | 真实 MySQL 领取、租约恢复、失败回滚与批次隔离 | | `CandidateScoringRefreshWorkerLoopIsolationTest` | 3 | 单项处理及失败登记异常不终止 Worker 循环 | | `CandidateFeedbackRefreshRegistrationTest` | 7 | Feedback create/update/delete 均登记,失败路径无孤立任务 | | `OfferExpiryRefreshRegistrationTest` | 7 | Offer 自动过期与任务同事务,重复执行合并且无消息副作用 | | `ResumeVersionRefreshAtomicityTest` | 4 | 简历版本与任务原子提交、外层回滚一致、重复登记合并 | 本轮之前完成的 `mvn clean verify` 为 **2800/2800,F=0 E=0 S=0**,已包含上述测试及评分消费者回归; OpenAPI 保持 **176/214**。 ### 真实 HTTP/MySQL 链路证据的有效性核查 归档独立验收 `docs/r01-d4-2-claim-token-final-verification-20260916.md` 已在真实 JAR、真实 MySQL、 Redis 与 RabbitMQ 环境执行五条写侧链路,结果 **44/44,FAIL=0,SKIP=0**: - Offer 自动过期; - CandidateFeedback create、update、delete; - 简历版本创建。 每条均证明“业务数据变化 → 同学生/周期唯一 refresh task → Worker 处理为 COMPLETED → 八维快照齐备 → 相同输入重放不推进 snapshot version → 不写 snapshot_history”。消费者趋势、排名、看板、申诉和 公平性监控另有 **33/33** 回归证据。 `git log` 逐文件核查显示,刷新包、`CandidateFeedbackService`、`OfferExpiryWorker` 和 `ResumeVersionWriter` 的最后一次生产修改均为已验收提交 `52c8026`;此后没有生产变更。因此归档 HTTP 证据与本轮重新执行的当前代码完全对应,不存在拿旧实现结果替代当前实现的问题。历史脚本依赖已失效的 Token 上下文,本轮未直接复用旧 Token,也未把旧上下文伪装为新执行结果。 ### 数据安全与边界 - V44/V45 仍是刷新基础设施仅有的两项迁移,V1–V43 未修改,无 V46; - claim token 只作为数据库内部所有权凭证,不进入日志、审计、响应、通知、消息或快照 JSON; - Feedback、Offer、Resume 的失败/回滚路径不会留下孤立任务; - 任务重复登记按学生与周期合并,值未改变时八维快照 version 不推进; - 写侧登记本身不发送 RabbitMQ 消息、不写通知或快照历史; - 本轮未改生产代码、测试、配置、迁移、接口或数据库数据。 ### 写侧刷新链路最终判定 **评分写侧事件 → refresh task → Worker → 八维快照刷新链路通过最终复验,可以冻结 R01-D4 运行时验收项。** 至此,最终总验收的功能与真实数据库运行项已经全部完成;下一步仅进行 Git 收口:独立审阅并精确提交 管理员初始化修复及总验收报告,随后在网络可用时推送 DEF-01 与该提交,确认 HEAD/upstream 0/0。 ## 最终总判定 > **PASS:S01、S02、S03、Q01、R01 的独立代码复验、自动化回归与真实 MySQL/Docker 黑盒验收全部完成,核心项 FAIL=0、SKIP=0,可以结束整改开发并进入 PR 审查。** 最终收口代码提交: - DEF-01 岗位公开列表关键词规范化:`0b2ca79b96ea585dbafc753a980f7f79f5cf15db`; - 管理员初始化严格幂等校验:`443a305`(本报告提交时的本地提交短 hash)。 最终有效门禁为 `mvn clean verify` **2800/2800,Failures=0、Errors=0、Skipped=0**; OpenAPI 静态与运行时均为 **176 paths / 214 operations**。管理员初始化 M1–M3、Q01 跨库与 EXPLAIN、 评分写侧刷新链路均已在本报告末尾完成独立收口;前文历次 FAIL、脚本缺陷与观察项作为审计轨迹保留, 不代表最终状态。 保留的非阻断观察项包括:Spring Boot 自动配置产生的惰性 generated security password 日志、历史 `SMS_SEND` 审计数据中的旧手机号、未读数包含本人发送但未读消息、Q01 小样本执行计划的全表扫描与 filesort,以及 Hikari 偶发连接校验告警。上述项目均已界定范围,未被隐去,也不改变本轮 PASS 判定。