You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

129 KiB

职来后端 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:<file> 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 -- <migration dir>  → 空(V1–V41 零修改)
git diff --diff-filter=D --name-only origin/master...HEAD -- <migration dir>  → 空(零删除)
ls <migration dir> | 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<connection.close>(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 <token> 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 <token> 0 —
应用日志 11 位手机号 0 —
应用日志 authorization 0 —
应用日志 password 2 Spring Boot 默认 Using generated security password=<uuid>;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=<uuid> 否 日志噪声 + 潜在的惰性默认账号 应用日志第 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=<NULL> <NULL> ✔
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(-))

String normalizedKeyword = keyword == null || keyword.isBlank() ? null : keyword.trim();
Page<Job> 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)

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() 首行):
    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 判定。